基于Mono.Cecil用C#为DLL所有方法注入秒表(含多返回方法)
解决方法:注入秒表计时到DLL的所有方法(含多返回场景)
看起来你需要给target.dll里的每个方法(不管返回类型、有没有多个return语句)注入秒表的启停逻辑,来统计方法执行耗时。原注入器的问题在于只处理了方法的首尾指令,没法覆盖多返回分支的场景,还带了无用的字符串加载操作。下面是修正后的完整实现,以及关键细节的解释:
修正后的注入器代码
using Mono.Cecil; using Mono.Cecil.Cil; using System.Linq; class Trial_injector { static void Main(string[] args) { // 加载目标DLL和秒表DLL,开启读写模式 var targetReadParams = new ReaderParameters { ReadWrite = true }; AssemblyDefinition targetAssembly = AssemblyDefinition.ReadAssembly("targetDll.dll", targetReadParams); var targetModule = targetAssembly.MainModule; var sourceReadParams = new ReaderParameters { ReadWrite = true }; AssemblyDefinition sourceAssembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", sourceReadParams); var sourceModule = sourceAssembly.MainModule; // 定位秒表类的启停方法 MethodDefinition startMethod = null; MethodDefinition stopMethod = null; foreach (var type in sourceModule.Types) { if (type.Name == "stopwatch_class") { startMethod = type.Methods.First(m => m.Name == "stopwatch_start"); stopMethod = type.Methods.First(m => m.Name == "stopwatch_stop"); break; } } // 将秒表方法导入到目标模块,确保跨模块调用合法 var importedStart = targetModule.ImportReference(startMethod); var importedStop = targetModule.ImportReference(stopMethod); // 遍历目标DLL的所有用户定义类型和方法 foreach (var type in targetModule.Types) { // 跳过编译器自动生成的特殊类型(比如模块类) if (type.IsSpecialName || type.IsRuntimeSpecialName) continue; foreach (var method in type.Methods) { // 跳过无方法体、抽象或原生方法,避免注入错误 if (!method.HasBody || method.IsAbstract || method.IsNative) continue; var ilProcessor = method.Body.GetILProcessor(); // 1. 在方法最开始注入start方法 var firstInstruction = method.Body.Instructions.First(); var callStart = ilProcessor.Create(OpCodes.Call, importedStart); ilProcessor.InsertBefore(firstInstruction, callStart); // 2. 在所有return指令前注入stop方法 // 先收集所有ret指令,避免遍历过程中修改指令集合导致异常 var retInstructions = method.Body.Instructions.Where(inst => inst.OpCode == OpCodes.Ret).ToList(); foreach (var retInst in retInstructions) { var callStop = ilProcessor.Create(OpCodes.Call, importedStop); ilProcessor.InsertBefore(retInst, callStop); } } } // 保存修改后的目标DLL targetAssembly.Write(); } }
核心改进点说明
- 移除冗余代码:删掉了原代码中无用的
ldstr指令,因为秒表的启停方法不需要字符串参数。 - 覆盖多返回场景:通过查找方法体内所有
OpCodes.Ret指令,在每个return前都插入stop调用,确保不管走哪个分支,都会正确停止计时并输出耗时。 - 跨模块调用优化:用
ImportReference正确导入秒表方法到目标模块,避免跨DLL调用失败。 - 过滤无效方法:跳过抽象、原生或无方法体的方法,防止注入过程中出现错误。
- 类型过滤:只处理用户定义的类型(比如
targetDll),跳过编译器生成的特殊类型。
效果验证
以你的func2为例,注入后IL代码会在两个return指令前都插入stopwatch_stop(),同时在方法开头插入stopwatch_start()——不管代码走哪个分支,都会完整统计方法的执行耗时。
注意事项
- 注入器项目需要安装
Mono.CecilNuGet包才能编译运行。 - 操作前记得备份
target.dll和stopwatch.dll,避免文件损坏。 - 如果目标方法包含try-catch-finally块,建议在finally块末尾也插入stop方法,确保异常场景下也能停止计时。
内容的提问来源于stack exchange,提问作者sugan_2416
相关产品推荐
相关产品推荐

