Search code examples
c#cilmono.cecil

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


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)

Solution

  • 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().