2016-07-15 2 views
2

Im пытается сгенерировать новый класс/объект во время выполнения.Reflection.Emit throws BadImageFormatException

После прочтения How to create a private property using PropertyBuilder, мне удалось реализовать все, и все так, как будто оно мне нужно.

Но как только им пытаются создать экземпляр моего нового объекта, им получать BadImageFormatException

Это, кажется, подобная проблема, но неразрешенный Is there any way to instrument System.Reflection.Emit?

Вот мой код:

поле:

internal class Field { 
     public string FieldName; 
     public Type FieldType; 
     public string Value; 
    } 

Генератор-код:

var xx = new List<Field>(new[] { new Field { FieldName = "Name", FieldType = typeof(string), Value = "Hello World" }, 
     new Field { FieldName = "Id", FieldType = typeof(int), Value = "1" } }); 
     this.DoVodoo(xx); 

Волшебное

private dynamic DoVodoo(IEnumerable<Field> fields) { 
     var aName = new AssemblyName("DynamicAssemblyExample"); 
     var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave); 

     var mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll"); 

     // Create class with all needed Properties 
     var tb = mb.DefineType("ParamRow", TypeAttributes.Public, typeof(object)); 
     foreach (var field in fields) { 
     var pb = tb.DefineProperty(field.FieldName, PropertyAttributes.None, CallingConventions.HasThis, field.FieldType, null); 

     var getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; 
     // Define the "get" accessor method for the Property. 
     var custNameGetPropMthdBldr = tb.DefineMethod($"get_{field.FieldName}", getSetAttr, typeof(string), Type.EmptyTypes); 

     var custNameGetIL = custNameGetPropMthdBldr.GetILGenerator(); 

     custNameGetIL.Emit(OpCodes.Ldarg_0); 
     custNameGetIL.Emit(OpCodes.Ldfld, custNameGetPropMthdBldr); 
     custNameGetIL.Emit(OpCodes.Ret); 

     // Define the "set" accessor method for CustomerName. 
     var custNameSetPropMthdBldr = tb.DefineMethod($"set_{field.FieldName}", getSetAttr, null, new[] { typeof(string) }); 

     var custNameSetIL = custNameSetPropMthdBldr.GetILGenerator(); 

     custNameSetIL.Emit(OpCodes.Ldarg_0); 
     custNameSetIL.Emit(OpCodes.Ldarg_1); 
     //custNameSetIL.Emit(OpCodes.Stfld, custNameGetPropMthdBldr); 
     custNameSetIL.Emit(OpCodes.Stfld, custNameSetPropMthdBldr); 
     custNameSetIL.Emit(OpCodes.Ret); 

     // Last, we must map the two methods created above to our PropertyBuilder to 
     // their corresponding behaviors, "get" and "set" respectively. 
     pb.SetGetMethod(custNameGetPropMthdBldr); 
     pb.SetSetMethod(custNameSetPropMthdBldr); 
     } 

     var finalType = tb.CreateType(); 

     var result = new List<object>(); 

     foreach (var field in fields) { 
     var inst = ab.CreateInstance(finalType.Name); 
     finalType.GetProperty(field.FieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(inst, field.Value); //<-- Here comes the trouble 
     result.Add(inst); 
     } 
     return result;} 

любая помощь о том, как создать экземпляр моего недавно созданного типа ParamRow.

Бонус-вопрос: Почему существует BadImageFormatException?

Дополнительная информация:

  • .Net Framework 4.6.1-
  • Compiler-цель x86
  • Никогда не делал Reflection.Emit до того
+1

Я собираюсь посмотреть на него в отладчик и посмотреть, если я могу обнаружить ошибку, но: если вы хотите хороший способ избежать их и получать хорошие сообщения об ошибках, попробуйте [Сигила] (HTTPS: //www.nuget.org/packages/Sigil /) - это оболочка вокруг испускания IL (но концептуально такая же) и предназначена для того, чтобы затруднить ее выполнение (или, по крайней мере, легко понять, почему он потерпел неудачу) –

ответ

3

.Message за исключением вы получаете важный бит:

Полевой токен за пределами допустимого диапазона.

Это говорит о том, что он не понимает, какое поле вы хотите использовать в ldfld/stfld - что, потому что вы передаете ему метода маркера (custNameGetPropMthdBldr/custNameSetPropMthdBldr) вместо полей маркер.

Вы должны определить и использовать поле:

var fb = tb.DefineField("__" + field.FieldName, field.FieldType, FieldAttributes.Private); 
// ... 
custNameGetIL.Emit(OpCodes.Ldarg_0); 
custNameGetIL.Emit(OpCodes.Ldfld, fb); 
custNameGetIL.Emit(OpCodes.Ret); 
// ... 
custNameSetIL.Emit(OpCodes.Ldarg_0); 
custNameSetIL.Emit(OpCodes.Ldarg_1); 
custNameSetIL.Emit(OpCodes.Stfld, fb); 
custNameSetIL.Emit(OpCodes.Ret); 

Отметим также, что это более эффективно использовать Type чем имя при создании объектов с помощью отражения; это работает отлично:

var inst = Activator.CreateInstance(finalType); 
+0

Вы, сэр, спасатель. Немного неудобно для меня, что я пропустил backingfield * -.- – lokusking

+0

@lokusking. Кажется, я помню, что Sigil показал бы эту ошибку во время компиляции; при вызове метода для выдачи доступа к полю, сигнатура метода ** требует **, чтобы вы передавали ему объект поля. Это отличная библиотека для того, чтобы сделать IL легче получить право. –

+0

Обычно я бы попробовал, но у компании-политики очень сложно получить материал от Nuget. Я попробую его дома – lokusking