Почему Mono.Cecil выступает за импорт метода, хотя я уже это сделал?

Вот мой код:

    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

    }

Этот код компилирует метод Write внутри Class1 сборки ToIL. Затем IL (инструкции) тела метода сохраняется в ils. Наконец, инструкции добавлены к каждому методу сборки EditAsm.exe.
Как предусмотрено, я импортировал WriteLine, но по-прежнему получаю следующее исключение в asmx.Write(@"d:\test.dll");

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


person Mohsen Sarkar    schedule 19.09.2015    source источник


Ответы (1)


Это связано с тем, что ваши инструкции IL принадлежат другому модулю, поэтому ссылки на методы в них недействительны для модуля, в который вы их добавляете. Импорт на самом деле просто создает ссылку на внешний метод (поле и т. д.), но эта ссылка действительна для конкретного модуля. Все ваши операнды вызова метода инструкции IL имеют ссылки, принадлежащие модулю с именем «assemblyName» в вашем коде. Эта строка:

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

на самом деле ничего не делает, потому что вы не используете возвращаемое значение. Как вы можете его использовать (в целом)? Как это:

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

Вы видите, что для создания инструкции по вызову метода требуется ссылка на метод для этого конкретного модуля, в который вы добавляете инструкцию. Теперь возможный способ решить вашу проблему:

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

Тогда ваш код больше не будет кидать.

person Evk    schedule 19.09.2015