2017-09-05 80 views
0

With Mono.Cecil看起来很简单,我们只需将目标MethodDefinitionBody设置为源MethodDefinitionBody即可。对于简单的方法,这工作正常。但是对于一些使用自定义类型的方法(例如初始化新对象),它不起作用(在编写程序集时抛出异常)。使用Mono.Cecil替换方法的Body与另一种方法的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我上面贴) 。但是,如果源法(在当前的应用程序定义)包含一些参考型号(如某些构造初始化...),就像这样:

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,您可以看到结果用于替换每个InstructionOperand。但对于这种情况下的类型引用,我完全不知道如何。

我希望有人在这里遇到Mono.Cecil可以帮我解决这个问题。我认为它应该很简单,但也许我不明白Mono.Cecil。感谢您的帮助。

+0

那岂不是更简单用新的方法的调用来代替身体? –

+0

@JeroenMostert这里的源代码'Test'方法只是一个简单的,实际上它可以是任何复杂的代码(包含几十行......)。因此,如果我们每次将这些代码手动转换为'Instructions',那就很难了,而且根本就没有趣味。我想用现有的方法替换另一个程序集中定义的另一个代码。我真的认为这对Mono.Cecil来说是可行的。 – Hopeless

+1

不,我的观点是 - 你想要的方法体已经被正确编译(包括类型和程序集引用以及整个hoopla)。为什么不用'Target.Test'调用替换'Source.Test'方法体呢?而不是尝试将它移植到新体中。 (如果单独程序集的存在是一个问题,则首先将它们组合起来。)无论源或目标的复杂程度如何,这都可以工作。 –

回答

0

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

“会员‘的System.Uri’在另一个模块中声明,并需从国外进口”

埋怨VariableDefinitionVariableType。我只是导入指令,而不是变量(它们只是从源MethodBody中完全引用)。因此,解决方案是我们需要以相同的方式导入变量(也可以导入ExceptionHandlers,因为ExceptionHandlerCatchType应该导入)。 这里只是类似的代码导入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); 
}