动态类型创建中的 MethodBuilder.CreateMethodBody() 问题

发布于 2024-10-08 09:54:18 字数 5085 浏览 3 评论 0 原文

对于实验,我尝试从源类型读取方法主体(使用 GetILasByteArray())并将其添加到新类型(使用 CreateMethodBody())。

我的源类就是这样 为该

public class FullClass
{
    public string Test(string data)
    {
        return data;
    }
    public string Test2(string data)
    {
        return data;
    }
    public string Test5(string data, string data1)
    {
        return data + data1;
    }
}

代码生成的 IL(使用反射器获取)

.method public hidebysig instance string Test(string data) cil managed
{
    .maxstack 1
    .locals init (
        [0] string CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005
    L_0005: ldloc.0 
    L_0006: ret 
}

但是从我的新类型生成的 IL 看起来像这样

.method public hidebysig virtual instance string Test(string) cil managed
{
    .maxstack 0
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005
    L_0005: ldloc.0 
    L_0006: ret 
}

差异是 maxstack 值和 maxstack 值.locals 指令。我不明白为什么我的实际类会生成局部变量,尽管它没有任何局部变量?

为什么 .maxstack 值存在差异,因为我使用源中的相同 IL 来创建新类型。

因此,我在调用该方法时收到错误“公共语言运行时检测到无效程序”

我创建动态类型的代码如下所示

public static class Mixin<Target>
    {

       public static Target compose<TSource>()
        {
            Type newType = null;

            AppDomain currentDom = Thread.GetDomain();

            AssemblyName DAssembly = new AssemblyName();
            DAssembly.Name = "DynamicTypesAssembly";

            AssemblyBuilder DAssemblyBldr = currentDom.DefineDynamicAssembly(
                               DAssembly,
                               AssemblyBuilderAccess.RunAndSave);



            ModuleBuilder DModuleBldr = DAssemblyBldr.DefineDynamicModule(DAssembly.Name, DAssembly.Name + ".dll", false);
         //   var DInterface = EmitInterface(DModuleBldr);
            TypeBuilder TypeBldr = DModuleBldr.DefineType("WorkOut.DType",
                    TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable
                    ,typeof(object), new[] { typeof(Target) });

            //TypeBldr.AddInterfaceImplementation(typeof(DInterface));

            var methodCol = typeof(Target).GetMethods(BindingFlags.Public| BindingFlags.Instance);

            foreach (var ms in methodCol)
            {
                var paramCol = ms.GetParameters();
                var paramTypeArray = paramCol.Select(x => x.ParameterType).ToArray();
                var paramNameArray = paramCol.Select(x=>x.Name).ToArray();
                MethodBuilder MthdBldr = TypeBldr.DefineMethod(ms.Name,
                                  MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
                                  ms.ReturnType,
                                  paramTypeArray);

                for(int i=0;i<paramCol.Count();i++)
                {
                    MthdBldr.DefineParameter(i+1, ParameterAttributes.None, paramNameArray[i]);
                }


                MethodInfo[] methodInfos = typeof(TSource).GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
                             BindingFlags.Static | BindingFlags.Instance);

                for (int i = 0; i < methodInfos.Count(); i++)
                {
                    var paramSrc = methodInfos[i].GetParameters();  
                    var paramSrcTypeArray = paramSrc.Select(x => x.ParameterType).ToArray();

                    if (methodInfos[i].Name == ms.Name && methodInfos[i].ReturnType == ms.ReturnType && paramSrc.Count() == paramCol.Count() && paramTypeArray.SequenceEqual(paramSrcTypeArray))
                     {
                        var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray();
                        var ilGen = MthdBldr.GetILGenerator();
                        //ilGen.Emit(OpCodes.Ldarg_0); //Load the 'this' reference onto the evaluation stack
                        //ilGen.Emit(OpCodes.Initobj);
                        MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length);
                        //ilGen.Emit(OpCodes.Ret);
                        break;
                    }
                }

            }
            newType = TypeBldr.CreateType();
            DAssemblyBldr.Save("a.dll");
            return (Target)Activator.CreateInstance(newType);  
        }

调用此代码的代码是

     var resMix = Mixin<ITest>.compose<FullClass>();
     var returned1 = resMix.Test("sam");

,ITest(目标)接口是

public interface ITest
{
     string Test(string data);     
}

编辑:

编辑:当注释此行时

  //var ilGen = MthdBldr.GetILGenerator();

maxstack 变为 .maxstack 16

我针对 PEverify 工具对新 dll 进行了检查,这给出了以下错误

WorkOut.DType::Test][offset 0x00000002] 无法识别的局部变量号。< /strong>

非常感谢任何帮助......:)

For an experiment, i am trying to read the method body (using GetILAsByteArray()) from source type and adding it to the new Type (Using CreateMethodBody()).

My source class is simply this

public class FullClass
{
    public string Test(string data)
    {
        return data;
    }
    public string Test2(string data)
    {
        return data;
    }
    public string Test5(string data, string data1)
    {
        return data + data1;
    }
}

The IL generated for this code (taken using reflector)

.method public hidebysig instance string Test(string data) cil managed
{
    .maxstack 1
    .locals init (
        [0] string CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005
    L_0005: ldloc.0 
    L_0006: ret 
}

But the IL generated from my new type looks like this

.method public hidebysig virtual instance string Test(string) cil managed
{
    .maxstack 0
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005
    L_0005: ldloc.0 
    L_0006: ret 
}

Differences are maxstack value & .locals directive. I dont understand why my the actual class generates the locals, though it doesnt have any local variables??

And why the differences in .maxstack value,since am using the same IL from the source to create the new Type.?

Due to this am getting an error "Common Language Runtime detected an invalid program" while calling the method.

My code creating the Dynamic type looks like this

public static class Mixin<Target>
    {

       public static Target compose<TSource>()
        {
            Type newType = null;

            AppDomain currentDom = Thread.GetDomain();

            AssemblyName DAssembly = new AssemblyName();
            DAssembly.Name = "DynamicTypesAssembly";

            AssemblyBuilder DAssemblyBldr = currentDom.DefineDynamicAssembly(
                               DAssembly,
                               AssemblyBuilderAccess.RunAndSave);



            ModuleBuilder DModuleBldr = DAssemblyBldr.DefineDynamicModule(DAssembly.Name, DAssembly.Name + ".dll", false);
         //   var DInterface = EmitInterface(DModuleBldr);
            TypeBuilder TypeBldr = DModuleBldr.DefineType("WorkOut.DType",
                    TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable
                    ,typeof(object), new[] { typeof(Target) });

            //TypeBldr.AddInterfaceImplementation(typeof(DInterface));

            var methodCol = typeof(Target).GetMethods(BindingFlags.Public| BindingFlags.Instance);

            foreach (var ms in methodCol)
            {
                var paramCol = ms.GetParameters();
                var paramTypeArray = paramCol.Select(x => x.ParameterType).ToArray();
                var paramNameArray = paramCol.Select(x=>x.Name).ToArray();
                MethodBuilder MthdBldr = TypeBldr.DefineMethod(ms.Name,
                                  MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
                                  ms.ReturnType,
                                  paramTypeArray);

                for(int i=0;i<paramCol.Count();i++)
                {
                    MthdBldr.DefineParameter(i+1, ParameterAttributes.None, paramNameArray[i]);
                }


                MethodInfo[] methodInfos = typeof(TSource).GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
                             BindingFlags.Static | BindingFlags.Instance);

                for (int i = 0; i < methodInfos.Count(); i++)
                {
                    var paramSrc = methodInfos[i].GetParameters();  
                    var paramSrcTypeArray = paramSrc.Select(x => x.ParameterType).ToArray();

                    if (methodInfos[i].Name == ms.Name && methodInfos[i].ReturnType == ms.ReturnType && paramSrc.Count() == paramCol.Count() && paramTypeArray.SequenceEqual(paramSrcTypeArray))
                     {
                        var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray();
                        var ilGen = MthdBldr.GetILGenerator();
                        //ilGen.Emit(OpCodes.Ldarg_0); //Load the 'this' reference onto the evaluation stack
                        //ilGen.Emit(OpCodes.Initobj);
                        MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length);
                        //ilGen.Emit(OpCodes.Ret);
                        break;
                    }
                }

            }
            newType = TypeBldr.CreateType();
            DAssemblyBldr.Save("a.dll");
            return (Target)Activator.CreateInstance(newType);  
        }

And code for invoking this is

     var resMix = Mixin<ITest>.compose<FullClass>();
     var returned1 = resMix.Test("sam");

Edit: And the ITest (Target) interface is

public interface ITest
{
     string Test(string data);     
}

EDIT:

when commenting this line

  //var ilGen = MthdBldr.GetILGenerator();

maxstack becomes .maxstack 16

I ran a check against the new dll against PEverify tool, this gives following error

WorkOut.DType::Test][offset 0x00000002] Unrecognized local variable number.

Any help really appreciated.... :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

荒人说梦 2024-10-15 09:54:19

正如关于 CreateMethodBody 的 MSDN 页面所述,这并不完全支持。

该实现很可能不会将 IL 解析为字节数组,因此它突然将 maxstack 设置为 16。

如果为该方法创建 ILGenerator,它将将该方法 maxstack 设置为零。当您使用不同的 Emit 重载时,ILGenerator 会增加它。由于您没有这样做并使用 CreateMethodBody,因此它保持为零。这解释了其中的差异。

对于只涉及简单代码的场景,CreateMethodBody 肯定是有问题的。每个采用元数据标记的操作码都将不可用,因为在创建字节数组时您不知道模块范围内的有限标记。并且它不允许您发出异常处理程序。

长话短说,CreateMethodBody 本身是毫无意义的。

如果您想继续实验,我建议您使用我的 反射 IL 阅读器 获取方法指令的表示,然后使用 ILGenerator 在方法构建器内重现方法主体。

As the MSDN page about CreateMethodBody says, this is not fully supported.

It's very probable that the implementation doesn't parse the IL a byte array, so it sets a maxstack to 16 out of the blue.

If you create an ILGenerator for the method, it will set the method maxstack to zero. The ILGenerator will increment it when you'll use the different Emit overloads. As you're not doing so, and use CreateMethodBody, it stays zeroed. This explains the difference.

CreateMethodBody is definitely problematic for scenarios which involves anything but simple code. Every opcode which takes a metadata token won't be usable, as you don't know the finite token in the scope of the module when you create a byte array. And it doesn't allow you to emit exception handlers.

Long story short, CreateMethodBody as it is, is pointless.

If you want to continue the experimentation, I suggest you use my reflection IL reader to get a representation of the Instruction of the methods, then use an ILGenerator to reproduce the method body inside a method builder.

陌伤浅笑 2024-10-15 09:54:19

好吧,您可以通过执行以下操作来克服“无法识别的局部变量号”错误:

var ilGen = MthdBldr.GetILGenerator();
foreach (var localVariable in methodInfos[i].GetMethodBody().LocalVariables)
{
    ilGen.DeclareLocal(localVariable.LocalType, localVariable.IsPinned);
}

我实际上可以在 .NET 3.5 / VS2008 中运行该程序,尽管它在 .NET 4.0 / VS2010 中仍然崩溃,可能是因为 maxstack 是错误的。如果您查看 Reflector 中的 TypeBuilder.CreateTypeNoLock,它会从 ilGenerator 中获取 maxStackSize(如果有),如果没有,则使用 16,因此您可能会被卡住。

您将遇到的更大问题是您正在复制元数据令牌 逐字节。来自 MSDN:

元数据标记是在
范围。例如,元数据令牌
与值 N 完全一致,
在给定范围内,记录
包含有关类型的详细信息
定义。然而,在不同的
范围,具有相同的元数据令牌
值 N 可能完全指定
不同的记录。

一旦您处理读取字段或调用另一个方法的方法,您就会收到一个神秘的错误,例如“MissingFieldException:找不到字段:'WorkOut.DType。”。

如果您确实想要复制方法,则需要解析 IL,使用 模块,例如Module.ResolveMember 将元数据令牌转换为 MemberInfo 对象,然后使用 ILGenerator.Emit 重载将它们转换为动态程序集中的新元数据标记。

这篇 CodeProject 文章解析方法体的 IL 将向您展示一种方法解析 IL。它使用 OpCodes 类型来构建映射从代码到操作码结构。您可以一一阅读说明并使用 OperandType 确定如何读取和翻译参数。

(请注意,我不建议在生产代码中执行任何此操作,但您说“用于实验”,这肯定会很有趣。)

Well, you can get past the "unrecognized local variable number" error by doing something like this:

var ilGen = MthdBldr.GetILGenerator();
foreach (var localVariable in methodInfos[i].GetMethodBody().LocalVariables)
{
    ilGen.DeclareLocal(localVariable.LocalType, localVariable.IsPinned);
}

I can actually run the program in .NET 3.5 / VS2008, although it still crashes in .NET 4.0 / VS2010, probably because maxstack is wrong. If you look at TypeBuilder.CreateTypeNoLock in Reflector, it pulls maxStackSize from the ilGenerator if there is one, and uses 16 if there is not, so you may be stuck.

The larger problem you'll run into is that you're copying metadata tokens byte-for-byte. From MSDN:

Metadata tokens are defined within a
scope. For example, a metadata token
with value N completely identifies,
within a given scope, a record that
contains details about a type
definition. However, in a different
scope, a metadata token with that same
value N might specify a completely
different record.

As soon as you process a method that reads a field or calls another method, you're going to get a mysterious error like "MissingFieldException: Field not found: 'WorkOut.DType.'."

If you really want to copy a method, you'll need to parse the IL, use the Reflection APIs on Module such as Module.ResolveMember to convert metadata tokens to MemberInfo objects, and then use ILGenerator.Emit overloads to convert those to new metadata tokens in your dynamic assembly.

This CodeProject article, Parsing the IL of a Method Body, will show you one way to parse the IL. It uses the OpCodes type to build a mapping from the code to the OpCode structure. You can read the instructions one-by-one and use OperandType to determine how to read and translate the argument.

(Note that I wouldn't recommend doing any of this in production code, but you say "for an experiment", and it will definitely be interesting.)

辞旧 2024-10-15 09:54:19

您需要在堆栈上重新声明参数才能使用它。 IL字节复制代码应该是这样的:

var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray();
ILGenerator ILGen = MthdBldr.GetILGenerator();
foreach (ParameterInfo parameter in paramSrc)
{
    ILGen.DeclareLocal(parameter.ParameterType);
}
MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length);

You need to redeclare args on the stack to be able to work with it. The IL bytes copy code should be like this:

var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray();
ILGenerator ILGen = MthdBldr.GetILGenerator();
foreach (ParameterInfo parameter in paramSrc)
{
    ILGen.DeclareLocal(parameter.ParameterType);
}
MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文