簡體   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