使用 Mono.Cecil 将方法的主体替换为另一个方法的主体?
Replace method's Body with Body of another method using Mono.Cecil?
使用 Mono.Cecil
看起来很简单,我们只需将目标 MethodDefinition
的 Body
设置为源 MethodDefinition
的 Body
。对于简单的方法,可以正常工作。但是对于某些使用自定义类型的方法(例如初始化一个新对象),它将不起作用(在写回程序集时抛出异常)。
这是我的代码:
//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");
上面的代码没有从任何地方引用,我只是自己尝试了一下,发现它适用于简单的情况(例如我上面发布的 2 种方法 Test
)。但是如果源方法(在当前应用程序中定义)包含一些类型引用(例如一些构造函数 init ...),就像这样:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
那么在回写程序集的时候就会失败。抛出的异常是 ArgumentException
,并带有以下消息:
"Member 'System.Uri' is declared in another module and needs to be imported"
事实上,我以前遇到过类似的消息,但它是针对方法调用的,例如 (string.Concat
)。这就是我尝试导入 MethodReference
的原因(您可以在我发布的代码中的 foreach
循环中看到 if
)。这确实适用于那种情况。
但这种情况不同,我不知道如何正确导入used/referenced类型(在本例中是System.Uri
)。据我所知,应该使用 Import
的结果,对于 MethodReference
,您可以看到该结果用于替换每个 Instruction
的 Operand
。但是对于这种情况下的类型参考,我完全不知道如何。
我在问题中发布的所有代码都很好,但还不够。实际上是异常信息:
"Member 'System.Uri' is declared in another module and needs to be imported"
抱怨 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);
}
使用 Mono.Cecil
看起来很简单,我们只需将目标 MethodDefinition
的 Body
设置为源 MethodDefinition
的 Body
。对于简单的方法,可以正常工作。但是对于某些使用自定义类型的方法(例如初始化一个新对象),它将不起作用(在写回程序集时抛出异常)。
这是我的代码:
//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");
上面的代码没有从任何地方引用,我只是自己尝试了一下,发现它适用于简单的情况(例如我上面发布的 2 种方法 Test
)。但是如果源方法(在当前应用程序中定义)包含一些类型引用(例如一些构造函数 init ...),就像这样:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
那么在回写程序集的时候就会失败。抛出的异常是 ArgumentException
,并带有以下消息:
"Member 'System.Uri' is declared in another module and needs to be imported"
事实上,我以前遇到过类似的消息,但它是针对方法调用的,例如 (string.Concat
)。这就是我尝试导入 MethodReference
的原因(您可以在我发布的代码中的 foreach
循环中看到 if
)。这确实适用于那种情况。
但这种情况不同,我不知道如何正确导入used/referenced类型(在本例中是System.Uri
)。据我所知,应该使用 Import
的结果,对于 MethodReference
,您可以看到该结果用于替换每个 Instruction
的 Operand
。但是对于这种情况下的类型参考,我完全不知道如何。
我在问题中发布的所有代码都很好,但还不够。实际上是异常信息:
"Member 'System.Uri' is declared in another module and needs to be imported"
抱怨 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);
}