繁体   English   中英

System.Reflection.Emit 自定义属性似乎与我的模型相同,但我得到了 InvalidProgramException

[英]System.Reflection.Emit custom property seems to be identical to my mock up, but I get an InvalidProgramException

为了扩展标题,我使用反射来生成一个自定义子类,该子类最终将具有任意数量的字符串属性,它们将在内部存储在字典中。 在这种情况下,我只使用一个。 我已经使用 Ildasm.exe 来获取 My Set 方法所需的 MSIL,因为调试器显示了我正在分配的值,但是当我尝试读回它时,我得到一个 InvalidProgramException,“检测到公共语言运行时一个无效的程序。” 它指向 get 方法。 我的获取方法 model 是:

/* public class TestWrapper : AttributeWrapper  //This is the source of the following MSIL
            {
                public string Name
                {
                    get { return GetAttribute("Name"); }
                    set { SetAttribute("Name", value); }
                }
            }
*/
    {
      // Code size       17 (0x11)
      .maxstack  2
      .locals init ([0] string V_0)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  ldstr      "Name"
      IL_0007:  call       instance string ConfigXMLParser.frmNodeBuilder/AttributeWrapper::GetAttribute(string)
      IL_000c:  stloc.0
      IL_000d:  br.s       IL_000f

      IL_000f:  ldloc.0
      IL_0010:  ret
    } // end of method TestWrapper::get_Name

生成相关属性的代码是:

  public static void CreateSelfNamingProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr =
                tb.DefineMethod("get_" + propertyName,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    typeof(string), Type.EmptyTypes);
            Type[] getAttributeArgs = { typeof(string) };
            Type basetype = tb.BaseType;
            MethodInfo getAttrBase = basetype.GetMethod("GetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);
            Debug.Assert(getAttrBase != null);

            ILGenerator ilGen = getPropMthdBldr.GetILGenerator();
            Label labelReturn = ilGen.DefineLabel();
            ilGen.Emit(OpCodes.Nop);           //The Get function starts here.  
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldstr, "Nope.");
            ilGen.EmitCall(OpCodes.Call, getAttrBase, getAttributeArgs);
            ilGen.Emit(OpCodes.Stloc_0);
            ilGen.Emit(OpCodes.Br_S, labelReturn);
            ilGen.MarkLabel(labelReturn);
            ilGen.Emit(OpCodes.Ldloc_0);
            ilGen.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig,
                    null, new[] { propertyType });

            MethodInfo setAttrBase
                = basetype.GetMethod("SetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();
            Type[] setParamTypes = { typeof(string) , typeof(string) };

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Nop); //The Set method starts here
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldstr, propertyName);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.EmitCall(OpCodes.Call, setAttrBase, setParamTypes);


            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }


// And this is what builds the TypeBuilder:

        public static TypeBuilder GetTypeBuilder()
        {
            string typeSignature = "MyDynamicType";
            AssemblyName an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

最后,把它们放在一起:

            TypeBuilder tb = GetTypeBuilder("TestType");
            CreateSelfNamingProperty(tb, "Name", typeof(string));
            dynamic instance = Activator.CreateInstance(tb.CreateType());
            instance.Name = "Test"; //Debug shows Name is Test, but
            MessageBox.Show(instance.Name);//Exception occurs here

基础 class 非常简单:

  public class AttributeWrapper
        {
            protected Dictionary<string, string> _attributes =
                new Dictionary<string, string>();

            protected void SetAttribute(string attribute, string value)
            {
                if (_attributes.ContainsKey(attribute))
                {
                    _attributes[attribute] = value;
                }
                else
                {
                    _attributes.Add(attribute, value);
                }
            }

            protected string GetAttribute(string attribute)
            {
                return _attributes.ContainsKey(attribute) ? _attributes[attribute] : "";
            }
        }

您的直接问题是您正在使用stloc.0ldloc.0操作码读取/写入本地,而没有定义任何本地:您可以通过在方法顶部调用以下内容来解决此问题:

ilGen.DeclareLocal(typeof(string));

现在事情就是这样,本地并不是真正需要的。 您反汇编并用作模板的代码显然是在Debug模式下编译的。 我可以说这是由于当地的原因,但主要来自nop的。 这两件事存在于调试版本中以帮助调试过程,允许您介入并查看中间值。 您的吸气剂可以简化为以下 IL:

ldarg.0
ldstr "Nope."
call instance string [AttributeWrapper]::GetAttribute(string)
ret

同样,您的 setter 可以删除其nop

其他一些旁注:

您使用错误的方法来发出call指令。 EmitCall方法仅用于调用varargs方法,并接受包含varargs参数类型的参数。 这不是你在这里所拥有的。 这要么涉及使用VarCall约定和/或TypedReference / __makeref__arglist / ArgIterator对 API 进行一些 p/invoke。 后者是“隐藏的” C# 关键字,您几乎不会在代码中找到它们。 早在 .NET 2.0 之前的日子里,当目标MethodInfo不是varargs时,该方法引发异常,但现在情况不再如此。

您应该改用普通的Emit方法并传递适当的CallCallVirt * OpCode

最后,我强烈建议为此使用SharpLab ,特别是将其设置为Release build 并查看 IL 选项卡。 这比编译代码然后手动反汇编容易得多。

* 通常你会看到人们使用后者,即使方法不是virtual

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM