Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MethodBuilder.CreateMethodBody() problem in Dynamic Type creation

For an experiment, i am trying to read the method body (using GetILAsByteArray()) from source type and adding it to the new Type (Using CreateMethodBody()).

My source class is simply this

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;
    }
}

The IL generated for this code (taken 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 maxstack value & .locals directive. I dont understand why my the actual class generates the locals, though it doesnt have any local variables??

And why the differences in .maxstack value,since am using the same IL from the source to create the new Type.?

Due to this am getting an error "Common Language Runtime detected an invalid program" while calling the method.

My code creating the Dynamic type looks like this

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 code for invoking this is

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

Edit: And the ITest (Target) interface is

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

EDIT:

when commenting this line

  //var ilGen = MthdBldr.GetILGenerator();

maxstack becomes .maxstack 16

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

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

Any help really appreciated.... :)

like image 331
RameshVel Avatar asked Dec 28 '22 03:12

RameshVel


2 Answers

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

It's very probable that the implementation doesn't parse the IL a byte array, so it sets a maxstack to 16 out of the blue.

If you create an ILGenerator for the method, it will set the method maxstack to zero. The ILGenerator will increment it when you'll use the different Emit overloads. As you're not doing so, and use CreateMethodBody, it stays zeroed. This explains the difference.

CreateMethodBody is definitely problematic for scenarios which involves anything but simple code. Every opcode which takes a metadata token won't be usable, as you don't know the finite token in the scope of the module when you create a byte array. And it doesn't allow you to emit exception handlers.

Long story short, CreateMethodBody as it is, is pointless.

If you want to continue the experimentation, I suggest you use my reflection IL reader to get a representation of the Instruction of the methods, then use an ILGenerator to reproduce the method body inside a method builder.

like image 53
Jb Evain Avatar answered Jan 29 '23 18:01

Jb Evain


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 actually run the program in .NET 3.5 / VS2008, although it still crashes in .NET 4.0 / VS2010, probably because maxstack is wrong. If you look at TypeBuilder.CreateTypeNoLock in Reflector, it pulls maxStackSize from the ilGenerator if there is one, and uses 16 if there is not, so you may be stuck.

The larger problem you'll run into is that you're copying metadata tokens byte-for-byte. From MSDN:

Metadata tokens are defined within a scope. For example, a metadata token with value N completely identifies, within a given scope, a record that contains details about a type definition. However, in a different scope, a metadata token with that same value N might specify a completely different record.

As soon as you process a method that reads a field or calls another method, you're going to get a mysterious error like "MissingFieldException: Field not found: 'WorkOut.DType.'."

If you really want to copy a method, you'll need to parse the IL, use the Reflection APIs on Module such as Module.ResolveMember to convert metadata tokens to MemberInfo objects, and then use ILGenerator.Emit overloads to convert those to new metadata tokens in your dynamic assembly.

This CodeProject article, Parsing the IL of a Method Body, will show you one way to parse the IL. It uses the OpCodes type to build a mapping from the code to the OpCode structure. You can read the instructions one-by-one and use OperandType to determine how to read and translate the argument.

(Note that I wouldn't recommend doing any of this in production code, but you say "for an experiment", and it will definitely be interesting.)

like image 30
Quartermeister Avatar answered Jan 29 '23 18:01

Quartermeister