MethodBuilder.CreateMethodBody () problem in creating a dynamic type

For the experiment, I try to read the body of the method (using GetILAsByteArray () ) from the source type and adding it to the new type (using CreateMethodBody () ).

My source class is just

public class FullClass { public string Test(string data) { return data; } public string Test2(string data) { return data; } public string Test5(string data, string data1) { return data + data1; } } 

IL generated for this code (using reflector)

 .method public hidebysig instance string Test(string data) cil managed { .maxstack 1 .locals init ( [0] string CS$1$0000) L_0000: nop L_0001: ldarg.1 L_0002: stloc.0 L_0003: br.s L_0005 L_0005: ldloc.0 L_0006: ret } 

But the IL generated from my new type looks like this:

 .method public hidebysig virtual instance string Test(string) cil managed { .maxstack 0 L_0000: nop L_0001: ldarg.1 L_0002: stloc.0 L_0003: br.s L_0005 L_0005: ldloc.0 L_0006: ret } 

Differences are the meaning of maxstack and .locals. I do not understand why my actual class generates locals, although it does not have any local variables

And why are there differences in the value of .maxstack, since I use the same IL from the source to create a new type.?

Because of this, I get the error "Common Language Runtime detected an invalid program when calling a method.

My code creating a dynamic type is as follows

 public static class Mixin<Target> { public static Target compose<TSource>() { Type newType = null; AppDomain currentDom = Thread.GetDomain(); AssemblyName DAssembly = new AssemblyName(); DAssembly.Name = "DynamicTypesAssembly"; AssemblyBuilder DAssemblyBldr = currentDom.DefineDynamicAssembly( DAssembly, AssemblyBuilderAccess.RunAndSave); ModuleBuilder DModuleBldr = DAssemblyBldr.DefineDynamicModule(DAssembly.Name, DAssembly.Name + ".dll", false); // var DInterface = EmitInterface(DModuleBldr); TypeBuilder TypeBldr = DModuleBldr.DefineType("WorkOut.DType", TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable ,typeof(object), new[] { typeof(Target) }); //TypeBldr.AddInterfaceImplementation(typeof(DInterface)); var methodCol = typeof(Target).GetMethods(BindingFlags.Public| BindingFlags.Instance); foreach (var ms in methodCol) { var paramCol = ms.GetParameters(); var paramTypeArray = paramCol.Select(x => x.ParameterType).ToArray(); var paramNameArray = paramCol.Select(x=>x.Name).ToArray(); MethodBuilder MthdBldr = TypeBldr.DefineMethod(ms.Name, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, ms.ReturnType, paramTypeArray); for(int i=0;i<paramCol.Count();i++) { MthdBldr.DefineParameter(i+1, ParameterAttributes.None, paramNameArray[i]); } MethodInfo[] methodInfos = typeof(TSource).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); for (int i = 0; i < methodInfos.Count(); i++) { var paramSrc = methodInfos[i].GetParameters(); var paramSrcTypeArray = paramSrc.Select(x => x.ParameterType).ToArray(); if (methodInfos[i].Name == ms.Name && methodInfos[i].ReturnType == ms.ReturnType && paramSrc.Count() == paramCol.Count() && paramTypeArray.SequenceEqual(paramSrcTypeArray)) { var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray(); var ilGen = MthdBldr.GetILGenerator(); //ilGen.Emit(OpCodes.Ldarg_0); //Load the 'this' reference onto the evaluation stack //ilGen.Emit(OpCodes.Initobj); MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length); //ilGen.Emit(OpCodes.Ret); break; } } } newType = TypeBldr.CreateType(); DAssemblyBldr.Save("a.dll"); return (Target)Activator.CreateInstance(newType); } 

And the code to call this

  var resMix = Mixin<ITest>.compose<FullClass>(); var returned1 = resMix.Test("sam"); 

Edit: And the ITest interface (Target)

 public interface ITest { string Test(string data); } 

EDIT:

when commenting on this line

  //var ilGen = MthdBldr.GetILGenerator(); 

maxstack becomes .maxstack 16

I checked the check against the new dll against the PEverify tool, this gives the following error

WorkOut.DType :: Test] [offset 0x00000002] Unrecognized local variable number.

Any help really appreciated .... :)

+4
source share
3 answers

As the MSDN page about CreateMethodBody shows , this is not fully supported.

It is very likely that the implementation does not parse the IL byte array, so it sets maxstack to 16 of blue.

If you create an ILGenerator for a method, it will set the maxstack method to zero. ILGenerator will increase it when you use various Emit overloads. Since you do not do this and use CreateMethodBody, it remains zero. This explains the difference.

CreateMethodBody is definitely problematic for scripts that include anything other than simple code. Each operation code that accepts a metadata token will not be used, since you do not know the final token in the module area when creating an array of bytes. And this does not allow you to allocate exception handlers.

In short, CreateMethodBody as it is is pointless.

If you want to continue the experiment, I suggest you use the IL-reader's reflector to get a view of the method instruction, then use ILGenerator to play the method body inside the method constructor.

+7
source

Well, you can get past the "unrecognized local variable number" error by doing something like this:

 var ilGen = MthdBldr.GetILGenerator(); foreach (var localVariable in methodInfos[i].GetMethodBody().LocalVariables) { ilGen.DeclareLocal(localVariable.LocalType, localVariable.IsPinned); } 

I can run the program in .NET 3.5 / VS2008, although it still crashes in .NET 4.0 / VS2010, possibly because maxstack is incorrect. If you look at TypeBuilder.CreateTypeNoLock in Reflector, it will pull maxStackSize from ilGenerator if it is, and uses 16 if it is not, so you can get stuck.

The big problem that you will encounter is that you are copying byte-for-byte metadata tokens . From MSDN:

Metadata identifiers are defined within scope. For example, a metadata token with a value of N fully identifies, within a given area, an entry that contains information about the type definition. However, in another scope, a completely different record may indicate a metadata token with the same value of N.

As soon as you process a method that reads a field or calls another method, you will get a mysterious error, such as "MissingFieldException: field not found:" WorkOut.DType. ".".

If you really want to copy the method, you need to parse the IL, use the Reflection API in Module , for example Module.ResolveMember , to convert metadata tokens into MemberInfo objects, and then use ILGenerator.Emit overloads to convert them to new metadata markers in your dynamic assembly .

In this CodeProject article, Parsing an IL of a method body , you will show you one way to parse an IL. It uses the OpCodes type to build a mapping from code to the OpCode structure. You can read the instructions one at a time and use OperandType to determine how to read and translate the argument.

(Note that I would not recommend doing this in production code, but you say β€œfor the experiment,” and it will definitely be interesting.)

+3
source

You need to update the arguments on the stack in order to be able to work with it. The IL byte coding code should look like this:

 var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray(); ILGenerator ILGen = MthdBldr.GetILGenerator(); foreach (ParameterInfo parameter in paramSrc) { ILGen.DeclareLocal(parameter.ParameterType); } MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length); 
0
source

Source: https://habr.com/ru/post/1332346/


All Articles