【问题标题】:MethodBuilder.CreateMethodBody() problem in Dynamic Type creation动态类型创建中的 MethodBuilder.CreateMethodBody() 问题
【发布时间】:2011-05-26 12:59:49
【问题描述】:

对于一个实验,我正在尝试从源类型读取方法主体(使用 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 值和 .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");

编辑:而ITTest(Target)接口是

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

编辑:

评论这一行时

  //var ilGen = MthdBldr.GetILGenerator();

maxstack 变为 .maxstack 16

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

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

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

【问题讨论】:

  • 嗨,Ramesh,您能否请求更多信息或接受答案?
  • @Jb,我只使用你的 IL 阅读器...忘记更新了..会改变它..

标签: c# reflection.emit dynamictype


【解决方案1】:

您需要在堆栈上重新声明 args 才能使用它。 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);

【讨论】:

  • 感谢您的信息。现在正在获取 .locals。但 .maxstack 仍然为 0。并且在 PEverify.exe 中出现“[offset 0x00000001] 堆栈溢出”错误。 :(
  • 这是您问题的答案。如果您需要更通用的解决方案,则必须逐条指令重写整个函数体,并且通常需要更好地理解 IL :-)
【解决方案2】:

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

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,它会从 ilGenerator 中提取 maxStackSize,如果没有则使用 16,因此您可能会卡住。

您将遇到的更大问题是您正在逐字节复制metadata tokens。来自 MSDN:

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

当您处理读取字段或调用另一个方法的方法时,您将收到类似“MissingFieldException:未找到字段:'WorkOut.DType.'”的神秘错误。

如果你真的想复制一个方法,你需要解析 IL,使用 Module 上的反射 API,例如 Module.ResolveMember 将元数据标记转换为 MemberInfo 对象,然后使用 ILGenerator.Emit 重载将它们转换为动态程序集中的新元数据标记。

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

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

【讨论】:

  • 谢谢伙计。正如您所说,局部变量问题已解决。但仍然没有解决堆栈大小错误。 :(
【解决方案3】:

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

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

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

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

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

如果你想继续实验,我建议你使用我的reflection IL reader 来获取方法指令的表示,然后使用 ILGenerator 在方法构建器中重现方法体。

【讨论】:

  • 感谢 Jb 指出 CreateMethodBody 中的限制,我会尝试反射 IL 阅读器。 :)
猜你喜欢
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多