使用Mono.Cecil将方法&Body的Body替换为另一种方法?

时间:2017-09-05 10:13:36

标签: c# reflection cil reflection.emit mono.cecil

使用Mono.Cecil,当我们可以将目标Body的{​​{1}}设置为来源MethodDefinition的{​​{1}}时,看起来非常简单。对于简单的方法,这可行。但对于某些方法,虽然使用了自定义类型(例如初始化一个新对象),但它不会起作用(在编写程序集时会抛出异常)。

这是我的代码:

Body

上面的代码没有从任何地方引用,我只是自己尝试过,发现它适用于简单的情况(例如我上面发布的2个方法MethodDefinition)。但是如果源方法(在当前应用程序中定义)包含一些Type引用(例如一些构造函数init ...),就像这样:

//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,其中包含以下消息:

  

"会员' System.Uri'在另一个模块中声明,需要导入"

事实上,我之前遇到过类似的消息,但是对于像(public class Form1 { public string Test(){ var u = new Uri("SomeUri"); return u.AbsolutePath; } } )这样的方法调用却是如此。这就是我尝试导入ArgumentException的原因(您可以在我发布的代码中看到string.Concat循环内的MethodReference)。这真的适用于那种情况。

但是这种情况不同,我不知道如何正确导入已使用/引用的类型(在本例中为if)。据我所知,应该使用foreach的结果,对于System.Uri,您可以看到结果用于替换每个Import的{​​{1}}。但对于这种情况下的Type参考,我完全不知道如何。

1 个答案:

答案 0 :(得分:1)

我的问题中发布的所有代码都很好,但还不够。实际上是异常消息:

  

"会员' System.Uri'在另一个模块中声明,需要导入"

抱怨VariableDefinition' 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);
}