2017-09-05 86 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); 
}