Mohsen Sarkar
Mohsen Sarkar

Reputation: 6030

Why Mono.Cecil argues for method import while I have done it already?

Here is my code :

    private void ModifyMethods()
    {
        SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;

namespace ToIL
{
    public class Class1
    {
        public void Write()
        {
            Console.WriteLine(""Hello"");
        }
    }
}");


        string assemblyName = System.IO.Path.GetRandomFileName();
        MetadataReference[] references = new MetadataReference[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
        };

        CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));


        Mono.Cecil.AssemblyDefinition asm = null;
        using (var ms = new MemoryStream())
        {
            var emitResult = compilation.Emit(ms);
            if (emitResult.Success)
            {
                ms.Seek(0, SeekOrigin.Begin);
                asm = Mono.Cecil.AssemblyDefinition.ReadAssembly(ms); 
            }
        }


        var class1 = asm.MainModule.Assembly.MainModule.Types.FirstOrDefault(T => T.Name == "Class1");
        var Method1 = class1.Methods.FirstOrDefault(M => M.Name == "Write");
        var ils = Method1.Body.Instructions;


        System.Reflection.MethodInfo mWriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
        Mono.Cecil.AssemblyDefinition asmx = Mono.Cecil.AssemblyDefinition.ReadAssembly(@"EditAsm.exe");
        var import = asmx.MainModule.Import(mWriteLine);
        foreach (var type in asmx.MainModule.Types)
        {
            if (type.Name == "<Module>") continue;
            foreach (var method in type.Methods)
            {
                var cilWorker = method.Body.GetILProcessor();
                foreach (var il in ils) cilWorker.Append(il);
            }
        }

        asmx.Write(@"d:\test.dll"); // Import Exception

    }

What this code does is that compiles Write method inside Class1 of assembly ToIL. Then the IL(Instructions) of method body is stored in ils. Finally the instructions are added to every method of EditAsm.exe assembly.
As is provided I have imported WriteLine but still geting following exception at asmx.Write(@"d:\test.dll");

Member 'System.Void System.Console::WriteLine(System.String)' is declared in another module and needs to be imported

Upvotes: 5

Views: 1420

Answers (1)

Evk
Evk

Reputation: 101473

That's because your IL instructions belong to another module, so method references in them are invalid for the module you are adding them to. Import actually just creates a reference to external method (field, etc), but this reference is valid for specific module. All your IL instruction method call operands have references that belong to module which named "assemblyName" in your code. This line:

var import = asmx.MainModule.Import(mWriteLine);

just does nothing actually, because you don't use the return value. How you can use it (in general)? Like this:

cilWorker.Append(Instruction.Create(OpCodes.Call, import));

You see that creating method call instruction requires method reference for this specific module you are adding instruction to. Now, possible way to fix your problem:

foreach (var type in asmx.MainModule.Types) {
      if (type.Name == "<Module>") continue;
      foreach (var method in type.Methods) {
          var cilWorker = method.Body.GetILProcessor();
          foreach (var il in ils) {
              // grab method reference
              var methodRef = il.Operand as Mono.Cecil.MethodReference;
              if (methodRef != null) {
                  // if it belongs to another module
                  if (methodRef.Module.Name == (assemblyName + ".dll")) {
                      // resolve it back to method definition and then import, 
                      // now to the correct module. Assign result back to opcode operand
                      il.Operand = asmx.MainModule.Import(methodRef.Resolve());
                  }
              }

              cilWorker.Append(il);
          }
      }
  }

Then your code will no longer throw.

Upvotes: 5

Related Questions