【发布时间】:2017-09-05 10:13:35
【问题描述】:
使用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");
上面的代码没有从任何地方引用,我只是自己尝试了一下,发现它适用于简单的情况(例如我上面发布的两种方法Test)。但是如果源方法(在当前应用中定义)包含一些类型引用(比如一些构造函数 init ...),像这样:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
然后它将在写回程序集时失败。抛出的异常是ArgumentException,并带有以下消息:
“成员'System.Uri'在另一个模块中声明,需要导入”
事实上,我之前也遇到过类似的消息,但它是针对方法调用的,例如 (string.Concat)。这就是我尝试导入MethodReference 的原因(您可以在我发布的代码中的foreach 循环中看到if)。这确实适用于这种情况。
但是这种情况不同,我不知道如何正确导入使用/引用的类型(在这种情况下是System.Uri)。我知道应该使用Import 的结果,对于MethodReference,您可以看到结果用于替换每个Instruction 的Operand。但是对于这种情况下的类型参考,我完全不知道如何。
【问题讨论】:
-
用调用新方法来替换body不是更简单吗?
-
@JeroenMostert 这里的源
Test方法只是一个简单的方法,实际上它可以是任何复杂的代码(包含几十行......)。因此,如果我们每次都手动将这些代码转换为Instructions,这将是困难且毫无意义的。我想使用现有方法替换另一个程序集中定义的另一个代码。我真的认为 Mono.Cecil 是可行的。 -
不,我的意思是——您想要的方法体已经正确编译(包括类型和程序集引用以及整个 hoopla)。与其尝试将其移植到新的主体中,不如将
Source.Test方法主体替换为对Target.Test的调用? (如果存在单独的程序集是一个问题,请先进行 ILMerge 它们。)无论源或目标有多复杂,这都会起作用。 -
@JeroenMostert 我想我不明白你的意思,我想替换的是
Target.Test,但只是它的Body。因此,当Target类(通过保存为新程序集进行修改后)在其他地方(不在我当前的应用程序中)使用时,Test方法将做我想要的(通过定义一个假方法Test- 以及作为源方法 - 在当前应用中)。 -
我认为你必须使用 reflexil.net 和 .Net Reflector 来完成它。
标签: c# reflection cil reflection.emit mono.cecil