Пользовательское свойство System.Reflection.Emit похоже на мой макет, но я получаю InvalidProgramException - PullRequest
1 голос
/ 08 мая 2020

Чтобы расширить заголовок, я использую Reflection для создания настраиваемого подкласса, который в конечном итоге будет иметь произвольное количество строковых свойств, которые они будут внутренне хранить в словаре. В данном случае я просто использую один. Я использовал Ildasm.exe, чтобы получить MSIL, который мне нужен для работы метода My Set, так как отладчик показывает значение, которое я присваиваю, но когда я пытаюсь прочитать его обратно, я получаю InvalidProgramException, «обнаружена среда CLR. неверная программа ". что указывает на метод 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] : "";
            }
        }

1 Ответ

1 голос
/ 09 мая 2020

Ваша непосредственная проблема заключается в том, что вы используете коды операций stloc.0 и ldloc.0 для чтения / записи в локальный компьютер без определения каких-либо локальных переменных! Вы можете исправить это, вызвав следующее в верхней части вашего метода:

ilGen.DeclareLocal(typeof(string));

Вот в чем дело, локальный адрес на самом деле не требуется. Код, который вы разобрали и использовали в качестве шаблона, очевидно, был скомпилирован в режиме Debug. Я могу сказать это по местным, но в основном по nop. Эти две вещи существуют в отладочной сборке для помощи в процессе отладки, позволяя вам вмешаться и просмотреть промежуточные значения. Ваш получатель может быть уменьшен до следующего IL:

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

Точно так же ваш сеттер может удалить его nop.

Некоторые другие примечания:

Вы используете неправильный метод для выдачи инструкции call. Метод EmitCall предназначен для вызова varargs только методов и принимает аргумент, содержащий типы параметров varargs. Это не то, что у вас здесь. Это либо включает в себя некоторый вызов p / для API с использованием соглашения VarCall и / или TypedReference / __makeref и __arglist / ArgIterator. Последние из них представляют собой «скрытые» C# ключевых слов, которые вы почти никогда не найдете в коде. Еще в дни, предшествующие. NET 2.0, метод вызывал исключение, когда цель MethodInfo не была varargs, но это уже не так.

Вместо этого вы должны использовать обычный метод Emit и передать соответствующий Call или CallVirt* OpCode.

Наконец, я настоятельно рекомендую использовать SharpLab для этой цели, в частности, установив для него Release build и просмотрев вкладку IL. Это целая проще, чем компилировать код и затем дизассемблировать вручную.

* Часто вы увидите, что люди используют последнее, даже если метод не virtual.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...