Reputation: 1058
I have the following code
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(TestType) },
typeof(Program).Module, false);
var add2Body = typeof(Program).GetMethod("add2").GetMethodBody().GetILAsByteArray();
var dynamicIlInfo = dynamicAdd2.GetDynamicILInfo();
var ilGenerator = dynamicAdd2.GetILGenerator();
dynamicIlInfo.SetLocalSignature(SignatureHelper.GetLocalVarSigHelper().GetSignature());
dynamicIlInfo.SetCode(add2Body, 1000);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>));
var ret2 = test2(new TestType()); // <-- Exception
the add2:
public string add2(TestType digit)
{
return digit.Name;
}
the testType:
public class TestType
{
public string Name = "test";
}
I get a InvalidProgrammException, no more information So I expect that the creation of the dynamic method fails. I think the dynamic Method can not find the references to the TestClass. Or what can be wrong in this case? Or what can I do to get a hint where the problem lies? the Exception brings not the needed infos...
Upvotes: 2
Views: 292
Reputation: 6207
You cannot directly copy IL stream from existing method to dynamic method, because IL uses so called tokens (32-bit numbers) to represent types, methods or fields. For the same field, value of token can be different in different modules, so byte-copying method IL stream without replacing tokens results in invalid program.
Second problem is that because add2 is instance method (not static), you must add instance of type that this method belongs to as first argument of method. In C# this first argument of instance methods is hidden, but IL requires it. Or you can declare method as static to avoid this.
Third problem is that add2 method contains (compiler generated) local variable. You have to add this variable to local signature (using SetLocalSignature() method), otherwise your method would use undeclared variable. (See code bellow to see how to do that).
First solution is to use GetILGenerator() instead of GetDynamicILInfo(), and rewrite IL stream from scratch. You can use IL disassembler (e.g. ILDASM, .NET Reflector) to get list of instructions for any existing method. Writing these instructions to IlGenerator using IlGenerator.Emit(...) should not be difficult.
static void Main(string[] args)
{
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(Program), typeof(TestType) },
typeof(Program).Module,
false);
var ilGenerator = dynamicAdd2.GetILGenerator();
ilGenerator.DeclareLocal(typeof(string));
ilGenerator.Emit(OpCodes.Ldarg_1);
var fld = typeof(TestType).GetField("Name");
ilGenerator.Emit(OpCodes.Ldfld, fld);
ilGenerator.Emit(OpCodes.Ret);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>), new Program());
var ret2 = test2(new TestType());
}
If you cannot use IlGenerator and you require direct IL stream manipulation using GetDynamicILInfo, you have to replace tokens in IL stream with values that are valid for generated dynamic method. Replacing tokens generally requires you to know offsets of these tokens in IL stream. Problem is that exact offset depends on compiler (and is even different for Release/Debug build). So you either have to use some IL dissassembler to get these offsets, or write IL parser able to do that (which is not trivial, maybe you can find some library for that). So following code uses kind of "dirty hack" to make it work in this particular case, but does not work generally.
public static void Main()
{
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(Program), typeof(TestType) },
typeof(Program).Module,
false);
var add2Body = typeof(Program).GetMethod("add2").GetMethodBody();
var add2ILStream = add2Body.GetILAsByteArray();
var dynamicIlInfo = dynamicAdd2.GetDynamicILInfo();
var token = dynamicIlInfo.GetTokenFor(typeof(TestType).GetField("Name").FieldHandle);
var tokenBytes = BitConverter.GetBytes(token);
//This tries to find index of token used by ldfld by searching for it's opcode (0x7B) in IL stream.
//Token follows this instructions so I add +1. This works well for this simple method, but
//will not work in general case, because IL stream could contain 0x7B on other unrelated places.
var tokenIndex = add2ILStream.ToList().IndexOf(0x7b) + 1;
Array.Copy(tokenBytes, 0, add2ILStream, tokenIndex, 4);//
//Copy signature of local variables from original add2 method
var localSignature = SignatureHelper.GetLocalVarSigHelper();
var localVarTypes = add2Body.LocalVariables.Select(_ => _.LocalType).ToArray();
localSignature.AddArguments(localVarTypes, null, null);
dynamicIlInfo.SetLocalSignature(localSignature.GetSignature());
dynamicIlInfo.SetCode(add2ILStream, 1);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>));
var ret2 = test2(new TestType());
}
Upvotes: 1