I want to use Mono. Cecil to create a delegate, but I don't know how to get its constructor

Viewed 311

I want to be able to generate il for the code

InjectBake.AddConstruction(typeof(Tests.TestConst), new Func<object[], IServiceProvider, object>[]
{
    Test1,
    Test2
});

I saw this code in decompilation , I dot`t know how to get its construction ,

newobj instance void class [mscorlib]System.Func`3<object[], class [mscorlib]System.IServiceProvider, object>::.ctor(object, native int)
1 Answers

Suposing you want to generate the folowing class:

using System;

class Test
{
    static string M(int i) { return i.ToString(); }
  
    void Foo()
    {
           Func conv = M;
    }
}

You can use Cecilifier to generate the following code:

using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using System; 
using System.Linq;
using BindingFlags = System.Reflection.BindingFlags;

using Cecilifier.Runtime;
               
public class SnippetRunner
{
    public static void Main(string[] args)
    {
        using(var assembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("Test", Version.Parse("1.0.0.0")), "moduleName", ModuleKind.Dll))
        {
            var t1 = new TypeDefinition("", "Test", TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.NotPublic, assembly.MainModule.TypeSystem.Object);
            assembly.MainModule.Types.Add(t1);
            t1.BaseType = assembly.MainModule.TypeSystem.Object;
            var Test_ctor_ = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName, assembly.MainModule.TypeSystem.Void);
            t1.Methods.Add(Test_ctor_);
            var il1 = Test_ctor_.Body.GetILProcessor();
            var Ldarg_02 = il1.Create(OpCodes.Ldarg_0);
            il1.Append(Ldarg_02);
            var Call3 = il1.Create(OpCodes.Call, assembly.MainModule.ImportReference(TypeHelpers.DefaultCtorFor(t1.BaseType)));
            il1.Append(Call3);
            var Ret4 = il1.Create(OpCodes.Ret);
            il1.Append(Ret4);
            

            //Method : M
            var Test_M_int32 = new MethodDefinition("M", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig, assembly.MainModule.TypeSystem.String);
            t1.Methods.Add(Test_M_int32);
            var il_Test_M_int32 = Test_M_int32.Body.GetILProcessor();

            //Parameters of 'static string M(int i) { return i.ToString(); }'
            var i5 = new ParameterDefinition("i", ParameterAttributes.None, assembly.MainModule.TypeSystem.Int32);
            Test_M_int32.Parameters.Add(i5);

            //return i.ToString(); 
            var Ldarga6 = il_Test_M_int32.Create(OpCodes.Ldarga, i5);
            il_Test_M_int32.Append(Ldarga6);
            var Call7 = il_Test_M_int32.Create(OpCodes.Call, assembly.MainModule.ImportReference(TypeHelpers.ResolveMethod("System.Private.CoreLib", "System.Int32", "ToString",System.Reflection.BindingFlags.Default|System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public,"")));
            il_Test_M_int32.Append(Call7);
            var Ret8 = il_Test_M_int32.Create(OpCodes.Ret);
            il_Test_M_int32.Append(Ret8);

            //Method : Foo
            var Test_Foo_ = new MethodDefinition("Foo", MethodAttributes.Private | MethodAttributes.HideBySig, assembly.MainModule.TypeSystem.Void);
            t1.Methods.Add(Test_Foo_);
            var il_Test_Foo_ = Test_Foo_.Body.GetILProcessor();

            //Func<int, string> conv = M;
            var lv_conv9 = new VariableDefinition(assembly.MainModule.ImportReference(typeof(System.Func<,>)).MakeGenericInstanceType(assembly.MainModule.TypeSystem.Int32,assembly.MainModule.TypeSystem.String));
            Test_Foo_.Body.Variables.Add(lv_conv9);
            var Ldnull10 = il_Test_Foo_.Create(OpCodes.Ldnull);
            il_Test_Foo_.Append(Ldnull10);
            var Ldftn11 = il_Test_Foo_.Create(OpCodes.Ldftn, Test_M_int32);
            il_Test_Foo_.Append(Ldftn11);
            var Newobj12 = il_Test_Foo_.Create(OpCodes.Newobj, assembly.MainModule.ImportReference(TypeHelpers.ResolveMethod("System.Private.CoreLib", "System.Func`2", ".ctor",System.Reflection.BindingFlags.Default|System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public,"System.Int32,System.String", "System.Object", "System.IntPtr")));
            il_Test_Foo_.Append(Newobj12);
            var Stloc13 = il_Test_Foo_.Create(OpCodes.Stloc, lv_conv9);
            il_Test_Foo_.Append(Stloc13);
            var Ret14 = il_Test_Foo_.Create(OpCodes.Ret);
            il_Test_Foo_.Append(Ret14);

            PrivateCoreLibFixer.FixReferences(assembly.MainModule);
            assembly.Write(args[0]);
        }
    }
}

In a simplified way, you'll need to:

  1. Get the method definition for which you want to change / add the code to. (Line #48)

  2. Get the ILProcessor for that method (Line #50)

  3. Inject the NewObj IL (Line #59)

Step 3 is the most important :

var Newobj12 = il_Test_Foo_.Create(
               OpCodes.Newobj,
               assembly.MainModule.ImportReference(
                   TypeHelpers.ResolveMethod(
                        "System.Private.CoreLib", 
                        "System.Func`2",
                        ".ctor",
                         System.Reflection.BindingFlags.Default|System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public,
                        "System.Int32,System.String", 
                        "System.Object", 
                        "System.IntPtr")));
    

Basically it is simply calling ILProcessor.Create() passing:

  1. The opcode (instruction) you want to create (Newobj in this case)
  2. A reference to the constructor of the object you want to instantiate.

For the step #2 above Cecilifier is using a helper function TypeHelpers.ResolveMethod() which basically uses reflection to retrieve the method and passing it to ModuleDefinition.ImportReference().

Related