Заменить тело метода на тело другого метода, используя Mono.Cecil?

С Mono.Cecil это выглядит довольно просто, когда мы можем просто установить Body цели MethodDefinition к Body источника MethodDefinition, Для простых методов это работает хорошо. Но для некоторых методов, в то время как используется пользовательский тип (например, для инициализации нового объекта), он не будет работать (за исключением исключения, возникающего во время обратной записи сборки).

Вот мой код:

//in current app
public class Form1 {
  public string Test(){
   return "Modified Test";
  }
}
//in another assembly
public class Target {
  public string Test(){
    return "Test";
  }
}

//the copying code, this works for the above pair of methods
//the context here is of course in the current app
var targetAsm = AssemblyDefinition.ReadAssembly("target_path");
var mr1 = targetAsm.MainModule.Import(typeof(Form1).GetMethod("Test"));
var targetType = targetAsm.MainModule.Types.FirstOrDefault(e => e.Name == "Target");
var m2 = targetType.Methods.FirstOrDefault(e => e.Name == "Test");
var m1 = mr1.Resolve();
var m1IL = m1.Body.GetILProcessor();

foreach(var i in m1.Body.Instructions.ToList()){
   var ci = i;
   if(i.Operand is MethodReference){
      var mref = i.Operand as MethodReference;
      ci = m1IL.Create(i.OpCode, targetType.Module.Import(mref));
   }
   else if(i.Operand is TypeReference){
      var tref = i.Operand as TypeReference;
      ci = m1IL.Create(i.OpCode, targetType.Module.Import(tref));
   }
   if(ci != i){
       m1IL.Replace(i, ci);
   }
}
//here the source Body should have its Instructions set imported fine
//so we just need to set its Body to the target's Body
m2.Body = m1.Body;
//finally write to another output assembly
targetAsm.Write("modified_target_path");

На приведенный выше код нигде не ссылались, я просто попробовал его сам и обнаружил, что он работает для простых случаев (например, для двух методов). Test Я разместил выше). Но если метод источника (определенный в текущем приложении) содержит ссылку на тип (например, конструктор init ...), например:

public class Form1 {
  public string Test(){
   var u = new Uri("SomeUri");
   return u.AbsolutePath;
  }
}

Тогда он не сможет записать сборку обратно. Исключение составляет ArgumentException со следующим сообщением:

"Элемент 'System.Uri' объявлен в другом модуле и должен быть импортирован"

На самом деле я сталкивался с подобным сообщением раньше, но оно для вызовов методов вроде (string.Concat). И именно поэтому я попытался импортировать MethodReference (вы можете увидеть if внутри foreach цикл в коде, который я разместил). И действительно, это сработало для этого случая. Но этот случай отличается, я не знаю, как импортировать используемые / ссылочные типы (в этом случае это System.Uri) правильно. Как я знаю результат Import следует использовать, для MethodReference Вы можете видеть, что результат используется для замены Operand для каждого Instruction, Но для ссылки на тип в этом случае я понятия не имею, как это сделать.

Я надеюсь, что кто-то здесь испытал с Mono.Cecil может помочь мне с этой проблемой. Я думаю, что это должно быть просто, но, возможно, я плохо понимаю Mono.Cecil. Спасибо за помощь.

1 ответ

Решение

Весь мой код, размещенный в моем вопросе, в порядке, НО не достаточно. На самом деле сообщение об исключении:

"Элемент 'System.Uri' объявлен в другом модуле и должен быть импортирован"

жалуется на VariableDefinition"s VariableType, Я просто импортирую инструкции, но не переменные (на которые просто ссылаются точно из источника MethodBody). Таким образом, решение заключается в том, что мы должны импортировать переменные таким же образом (и, возможно, импортировать ExceptionHandlers а потому что ExceptionHandler имеет CatchType который должен быть импортирован). Вот только аналогичный код для импорта VariableDefinition:

var vars = m1.Body.Variables.ToList();
m1.Body.Variables.Clear();
foreach(var v in vars){
   var nv = new VariableDefinition(v.Name, targetType.Module.Import(v.VariableType));
   m1.Body.Variables.Add(nv);
}
Другие вопросы по тегам