简体   繁体   English

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

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

To expand on the title, I'm using Reflection to generate a custom subclass that will eventually have an arbitrary number of string properties which they'll internally store in a dictionary.为了扩展标题,我使用反射来生成一个自定义子类,该子类最终将具有任意数量的字符串属性,它们将在内部存储在字典中。 In this case I'm just using one.在这种情况下,我只使用一个。 I've used Ildasm.exe to get the MSIL that I need for the My Set method works, as the debugger shows the value I'm assigning, but when I try to read it back I get an InvalidProgramException, "Common Language Runtime detected an invalid program."我已经使用 Ildasm.exe 来获取 My Set 方法所需的 MSIL,因为调试器显示了我正在分配的值,但是当我尝试读回它时,我得到一个 InvalidProgramException,“检测到公共语言运行时一个无效的程序。” which points back to the get method.它指向 get 方法。 My get method model is:我的获取方法 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

And code generating the property in question is:生成相关属性的代码是:

  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;
        }

Finally, putting it all together:最后,把它们放在一起:

            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

And the base class is pretty straightforward:基础 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] : "";
            }
        }

Your immediate problem is that you are using the stloc.0 and ldloc.0 opcodes to read/write into a local without having defined any locals: You can fix that by calling the following at the top of your method:您的直接问题是您正在使用stloc.0ldloc.0操作码读取/写入本地,而没有定义任何本地:您可以通过在方法顶部调用以下内容来解决此问题:

ilGen.DeclareLocal(typeof(string));

Now here's the thing, the local isn't really required.现在事情就是这样,本地并不是真正需要的。 The code you disassembled and used as your template was obviously compiled in Debug mode.您反汇编并用作模板的代码显然是在Debug模式下编译的。 I can tell this due to the local but mostly from the nop 's.我可以说这是由于当地的原因,但主要来自nop的。 These two things exist in the debug build to aide in the debugging process, allowing you to step in and view the intermediate values.这两件事存在于调试版本中以帮助调试过程,允许您介入并查看中间值。 Your getter can be reduced to the following IL:您的吸气剂可以简化为以下 IL:

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

Similarly, your setter can have its nop 's removed.同样,您的 setter 可以删除其nop

Some other side notes:其他一些旁注:

You are using the wrong method to emit your call instruction.您使用错误的方法来发出call指令。 The EmitCall method is for calling varargs methods only and accepts an argument containing the types of the varargs parameters. EmitCall方法仅用于调用varargs方法,并接受包含varargs参数类型的参数。 That's not what you have here.这不是你在这里所拥有的。 That either involves some p/invoke against an API using the VarCall convention and/or TypedReference / __makeref and __arglist / ArgIterator .这要么涉及使用VarCall约定和/或TypedReference / __makeref__arglist / ArgIterator对 API 进行一些 p/invoke。 The latter of which are "hidden" C# keywords that you will almost never find in code.后者是“隐藏的” C# 关键字,您几乎不会在代码中找到它们。 Way back in the days prior to .NET 2.0 the method threw an exception when the target MethodInfo wasn't varargs but this is not the case anymore.早在 .NET 2.0 之前的日子里,当目标MethodInfo不是varargs时,该方法引发异常,但现在情况不再如此。

You should instead use the normal Emit method and pass the appropriate Call or CallVirt * OpCode .您应该改用普通的Emit方法并传递适当的CallCallVirt * OpCode

Finally, I'd strongly suggest using SharpLab for this purpose, specifically setting it to Release build and viewing the IL tab.最后,我强烈建议为此使用SharpLab ,特别是将其设置为Release build 并查看 IL 选项卡。 It's a whole lot easier than compiling the code and then disassembling manually.这比编译代码然后手动反汇编容易得多。

* Often you will see people use the latter, even when the method isn't virtual . * 通常你会看到人们使用后者,即使方法不是virtual

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

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