问题
I am using IL generation to create a simple deserializer method which takes strings out of a Lucene document and sets properties or fields of a reference-type object (POCO).
Whenever I try to run the generated method, I receive a VerificationException error. There are other questions regarding this error, a few of which have to do with DynamicMethods, but from what I can tell the issue I am having is different.
operation-could-destablize-the-runtime-and-dynamicmethod-with-value-types
msil-operation-could-destabilize-the-runtime-exception
Although the method will get more complicated in the future, I have it just doing string assignment right now. The method I am trying to create dynamically will look exactly like this:
public static PocoObject ExampleMethod(Document document)
{
var poco = new PocoObject();
poco.ID = document.Get("ID");
poco.DisplayText = document.Get("DisplayText");
poco.PropId = document.Get("PropId");
return poco;
}
Where PocoObject looks like this:
class PocoObject
{
public string ID;
public string DisplayText;
public string PropId { get; set; }
}
I tried to replicate the IL generated from the compile-time code EXACTLY (even the unnecessary bits) and it looks like this:
.method public instance object 'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'(class [Lucene.Net]Lucene.Net.Documents.Document A_1) cil managed
{
// Code size 65 (0x41)
.maxstack 4
IL_0000: nop
IL_0001: newobj instance void [LukeMapperTest]LukeMapperTest.PocoObject::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldarg.0
IL_0009: ldstr "ID"
IL_000e: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0013: stfld string [LukeMapperTest]LukeMapperTest.PocoObject::ID
IL_0018: ldloc.0
IL_0019: ldarg.0
IL_001a: ldstr "DisplayText"
IL_001f: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0024: stfld string [LukeMapperTest]LukeMapperTest.PocoObject::DisplayText
IL_0029: ldloc.0
IL_002a: ldarg.0
IL_002b: ldstr "PropId"
IL_0030: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0035: callvirt instance void [LukeMapperTest]LukeMapperTest.PocoObject::set_PropId(string)
IL_003a: nop
IL_003b: ldloc.0
IL_003c: stloc.1
IL_003d: br.s IL_003f
IL_003f: ldloc.1
IL_0040: ret
} // end of method Test::'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'
I have managed to save the DynamicMethod to an assembly on disk, inspected it, and this is exactly what it brings up. Line for line it is the same as the compile-time method IL.
Nevertheless, when executing the dynamic method, the error above is thrown. Does anyone have any idea how I could fix this?
Note: I have the source code on GitHub if anyone would like to take a closer look. The IL Generation code is in LukeMapper.cs:GetDumbDeserializer() (line 133)
LukeMapper GitHub repo
All help is appreciated! Thanks!
EDIT: So I have simplified the IL generation code and it is essentially the following:
private static Func<Document, object> GetDumbDeserializer(Type type)
{
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);
var il = dm.GetILGenerator();
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
il.Emit(OpCodes.Nop);
il.DeclareLocal(type);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);
Label returnLabel = il.DefineLabel();
//stack is [target]
var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);
foreach (var setter in settableProperties)
{
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, setter.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.Emit(OpCodes.Stfld, setter.Field);
}
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Stloc_1); // stack is empty
il.Emit(OpCodes.Br_S, returnLabel);
il.MarkLabel(returnLabel);
il.Emit(OpCodes.Ldloc_1); // stack is [rval]
il.Emit(OpCodes.Ret);
return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));
}
I added in the il.DeclareLocal(type)
(where type is PocoObject) per kvbs's comment, but I'm not sure if I am putting it in the right place (or if it matters).
回答1:
It is the gibberish at the end, and using Stfld
to call a property; I have no idea where that end stuff came from, but I do not believe that came from ExampleMethod
- I think that might have been from a previous build of yours. In particular, you haven't defined a second local, so Ldloc_1
makes no sense; but there is absolutely no need for any labels / branches here. Here is what I have, that works (note I didn't know what your settableProperties
were, so I did it just using FieldInfo
/ PropertyInfo
:
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);
var il = dm.GetILGenerator();
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
il.DeclareLocal(type);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);
var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, field.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.Emit(OpCodes.Stfld, field);
}
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var setter = prop.GetSetMethod();
if (setter == null) continue;
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, prop.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.EmitCall(OpCodes.Callvirt, setter, null);
}
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));
And for comparison purposes, here is what I get when looking at ExampleMethod
in reflector (in a release build, etc):
.method public hidebysig static class PocoObject ExampleMethod(class Document document) cil managed
{
.maxstack 3
.locals init (
[0] class PocoObject poco)
L_0000: newobj instance void PocoObject::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldarg.0
L_0008: ldstr "ID"
L_000d: callvirt instance string Document::Get(string)
L_0012: stfld string PocoObject::ID
L_0017: ldloc.0
L_0018: ldarg.0
L_0019: ldstr "DisplayText"
L_001e: callvirt instance string Document::Get(string)
L_0023: stfld string PocoObject::DisplayText
L_0028: ldloc.0
L_0029: ldarg.0
L_002a: ldstr "PropId"
L_002f: callvirt instance string Document::Get(string)
L_0034: callvirt instance void PocoObject::set_PropId(string)
L_0039: ldloc.0
L_003a: ret
}
Things to note:
- no labels / branching (I have no idea where that came from, but that is not from what you posted)
- it defines a local
- no
nop
来源:https://stackoverflow.com/questions/11680490/prevent-dynamicmethod-verificationexception-operation-could-destabilize-the-ru