浅谈 C# Assembly 与 IL (一):C# Assembly 与 Reflection

前一阵子想利用闲余时间写一个 Unity 游戏的翻译工具,主要是用于翻译一些内嵌在代码中的文本,最初想偷懒看了一下网上的教学推荐说可以先利用DnSpy、ILSpy等工具反编译,直接修改反编译出来的代码中的字符串然后再重新编译,这样就只需要写一个提取和置换c#代码中所有文本的工具就行了。但在略微尝试一下后发现这些反编译工具并不能完美的生成可编译的代码,于是只能暂时搁置了。
刚好近期工作中在编写一些Debug工具,需要大量的利用 c# 的 Reflection 和 Mono.Cecil、ICSharpCode.Compiler等工具来读取程序集中的CIL,以及利用IL注入动态的生成一些Debug代码。在一段时间的学习后这些工具仿佛打开了新世界的大门,此前提到的翻译工具也不再是问题了,就想在这里分享一下成果,也算是对前段时间的所学做一些记录。

介绍

c#的代码在编译后,会先生成一个 Assembly 程序集(.exe, .dll),该程序集包含一些类似于汇编的中间语言(CIL)构成的托管代码,然后再通过类似于Java虚拟机的虚拟运行环境 CLR (Common Language Runtime) 来进行JIT (just-in-time) 编译成机器语言。而这篇文章以及之后整个专栏的目的就是介绍如何查看与编辑 c# 所编译出来的程序集中所包含的IL代码。

微软官方提供了 System.Reflection (反射) 来用于读取c#程序集中的内容,常常用于查看程序集中的内容并动态的生成其中类的实例,调用里面的方法。在不涉及太底层的分析时,反射能很好的达到目的并快速的应用。

使用反射(Reflection)读取程序集

反射作为c#官方提供的工具,本身使用起来非常的简单。你可以用它快速的读取当前程序的程序集,或者读取指定路径的程序集。

获取当前正在运行的程序集

Assembly currentAssem = Assembly.GetExecutingAssembly();

根据已有类读取

Assembly assembly = Typename.GetType().Assembly;

从文件中读取

Assembly assembly = Assembly.Loadfile("File path");

要注意的是,Assembly 只能读取指定版本的程序集,这个所谓的版本不单单是指的.Net Framework的版本,还有目标是x86架构还是x64。不同的架构可能会导致assembly读取失败。

生成类的动态实例,调用方法

当我们从 Assembly 中获得 Runtime Type 以及其方法信息以后,就可以利用Activator.CreateInstance() 来生成其动态的对象并调用其中方法了。

// 将该类生成的实例作为动态的对象, 直接调用方法 dynamic dynamicTypeInstance = Activator.CreateInstance(type); // 可以在type后面插入object数组作为 constructor 参数传入 // 动态类可以直接调用方法并填入参数 dynamicTypeInstance.MethodName(1, 2); // 将该类生成的实例作为 object, 通过 MethodInfo.Invoke 来调用方法 object typeInstance = Activator.CreateInstance(type); method.Invoke(typeInstance, new object[] {1, 2, "string"});

可能有人(比如我)会觉得用 dynamic 来调用方法速度会比method.invoke(object) 慢,但经过一番测试后,结果却令我感到惊讶。我使用的测试方法先编写一个简单的GetSum方法计算两个参数的和并返回,然后用如下代码分别计算动态调用、通过Method.Invoke调用、以及直接调用该方法 10000 次的总时间消耗和最大单次时间消耗。

Stopwatch watch = new Stopwatch(); watch.Start(); long initialTime = watch.ElapsedMilliseconds; dynamic dynamicTypeInstance = Activator.CreateInstance(typeof(Program)); long maxTime = 0; long maxTimeId = 0; // 动态调用方法一万次 for (int i = 0; i < 10000; i++) { var startTime = watch.ElapsedTicks; // 动态类可以直接调用方法并填入参数 dynamicTypeInstance.GetSum(i, i+1); var endTime = watch.ElapsedTicks; var timeDifference = endTime - startTime; if(timeDifference > maxTime) { maxTime = timeDifference; maxTimeId = i; } } Console.WriteLine("Dynamic"); Console.WriteLine("Max Time Id: " + maxTimeId); Console.WriteLine("Max Time: " + maxTime); Console.WriteLine("Total Time: " + (watch.ElapsedTicks - initialTime)); watch.Stop(); watch.Start(); initialTime = watch.ElapsedMilliseconds; object typeInstance = Activator.CreateInstance(typeof(Program)); MethodInfo method = typeof(Program).GetMethod("GetSum"); maxTime = 0; maxTimeId = 0; // 通过 Method.Invoke 调用一万次 for (int i = 0; i < 10000; i++) { var startTime = watch.ElapsedTicks; method.Invoke(typeInstance, new object[] { i, i + 1 }); var endTime = watch.ElapsedTicks; var timeDifference = endTime - startTime; if (timeDifference > maxTime) { maxTime = timeDifference; maxTimeId = i; } } Console.WriteLine("Method.Invoke"); Console.WriteLine("Max Time Id: " + maxTimeId); Console.WriteLine("Max Time: " + maxTime); Console.WriteLine("Total Time: " + (watch.ElapsedTicks - initialTime)); watch.Stop(); watch.Start(); Program program = new Program(); maxTime = 0; maxTimeId = 0; // 直接调用一万次 for (int i = 0; i < 10000; i++) { var startTime = watch.ElapsedTicks; program.GetSum(i, i + 1); var endTime = watch.ElapsedTicks; var timeDifference = endTime - startTime; if (timeDifference > maxTime) { maxTime = timeDifference; maxTimeId = i; } } Console.WriteLine("Direct Call"); Console.WriteLine("Max Time Id: " + maxTimeId); Console.WriteLine("Max Time: " + maxTime); Console.WriteLine("Total Time: " + (watch.ElapsedTicks - initialTime)); watch.Stop();

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwwpp.html