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

问题描述 投票:0回答:1

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

/* 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

基类非常简单:

  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] : "";
            }
        }
c# .net reflection
1个回答
0
投票

您的直接问题是,您正在使用stloc.0ldloc.0操作码在未定义任何本地变量的情况下读写本地变量!您可以通过在方法顶部调用以下内容来解决此问题:

ilGen.DeclareLocal(typeof(string));

现在这是东西,实际上并不需要。您反汇编并用作模板的代码显然是在Debug模式下编译的。我可以说是由于本地原因,但主要来自nop。这两件事在调试版本中存在,有助于调试过程,使您可以介入并查看中间值。您的吸气剂可以减少到以下IL:

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

类似地,您的二传手也可以删除其nop

[其他注意事项:

您使用了错误的方法来发出call指令。 EmitCall方法is for calling varargs methods only并接受包含varargs参数类型的参数。那不是你这里的。这要么涉及使用varargs约定和/或VarCall / TypedReference__makeref / __arglist针对API的一些p /调用。后者是“隐藏的” C#关键字,您几乎在代码中找不到它们。追溯到.NET 2.0之前的日子,当目标ArgIterator不是MethodInfo时,该方法引发了异常,但情况不再如此。

您应该改用常规的varargs方法并传递适当的EmitCall * CallVirt

最后,我强烈建议为此目的使用OpCode,特别是将其设置为SharpLab构建并查看IL选项卡。与编译代码然后手动拆卸相比,它[容易。

*您经常会看到人们使用后者,即使该方法不是Release
© www.soinside.com 2019 - 2024. All rights reserved.