C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!
前言
从C# 6.0开始,C#编译器就从以前由C++实现的csc.exe换成了用C#重新实现的开放式API式编译服务Roslyn。这个编译器到现在已经替代了老式编译器,从前WebForm的动态编译基础CSharpCodeProvider就是对csc.exe的调用包装,当然也继承了 csc.exe 的所有毛病。比如这是一个完全的黑盒编译器,把源码传进去返回程序集,中间的符号解析,词法、语法分析,优化和生成这些中间步骤究竟发生了什么我们无从知晓。无法作为脚本引擎把C#代码作为脚本执行,曾经有过第三方脚本引擎。当然还有不支持C# 6.0或更高版本源代码的编译,巨硬是铁了心要强推Roslyn逼死csc.exe啊,虽然都9102年了,csc.exe 早都透心凉了。
到VS 2019,C#又多了一个功能,导航到反编译的源代码,虽然之前已经有各种第三方反编译工具和VS集成扩展,但总归没有自带的来的方便。之前大名鼎鼎的dotPeek也在使用中发现无法反编译框架为.NetStandard 2.1和.NetCoreApp 3.1的程序集,不知道现在怎么样了。刚好在使用中发现了“由ICSharpCode.Decompiler提供反编译服务”字样,抱着好玩的心态去Nuget搜索了一下,居然真的找到了!既然这样,那岂不是能:把Roslyn 编译的程序集交给ICSharpCode.Decompiler反编译的源代码交给Roslyn编译的程序集交给ICSharpCode.Decompiler反编译的源代码交给…… (→_→)
正文
这两个包的基本使用其实很简单。在项目的管理Nuget程序包中搜索并安装 ICSharpCode.Decompiler和Microsoft.CodeAnalysis.CSharp.Scripting。
Microsoft.CodeAnalysis.CSharp.Scripting基本使用:
引用命名空间using Microsoft.CodeAnalysis.CSharp.Scripting;
开始使用:
//执行表达式,返回强类型结果 int result = await CSharpScript.EvaluateAsync<int>("1 + 2");
就这么简单,已经能执行一个简单的表达式了,连 C# 语句要分号这种规则都不用 (@0@)/
其他各种花式用法:要引用更多命名空间,包括但不限于:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting;
//处理编译错误 try { Console.WriteLine(await CSharpScript.EvaluateAsync("2+")); } catch (CompilationErrorException e) { Console.WriteLine("2+ : " + string.Join(Environment.NewLine, e.Diagnostics)); } //添加程序集引用 var result1 = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()", ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly)); Console.WriteLine("System.Net.Dns.GetHostName() : " + result1); //导入命名空间 using var result2 = await CSharpScript.EvaluateAsync("Directory.GetCurrentDirectory()", ScriptOptions.Default.WithImports("System.IO")); Console.WriteLine("Directory.GetCurrentDirectory() : " + result2); //导入静态类型 using static var result3 = await CSharpScript.EvaluateAsync("Sqrt(2)", ScriptOptions.Default.WithImports("System.Math")); Console.WriteLine("Sqrt(2) : " + result3); //参数化脚本 var globals = new Globals {X = 1, Y = 2}; Console.WriteLine("X + Y : " + await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals)); //编译缓存并多次执行脚本 var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals)); script.Compile(); for (int i = 0; i < 10; i++) { Console.WriteLine("No " + (i + 1) + " X*Y : " + (await script.RunAsync(new Globals { X = i, Y = i })).ReturnValue); } //编译脚本为委托 script = CSharpScript.Create<int>("X/Y", globalsType: typeof(Globals)); ScriptRunner<int> runner = script.CreateDelegate(); for (int i = 1; i < 11; i++) { Console.WriteLine("No " + (i + 1) + " X/Y : " + await runner(new Globals { X = new Random().Next(1,i), Y = new Random().Next(1, i) })); } //运行脚本片段并检查已定义的变量 var state = await CSharpScript.RunAsync<int>("int answer = 42;"); foreach (var variable in state.Variables) Console.WriteLine($"{variable.Name} = {variable.Value} of type {variable.Type}"); //连接多个片段为一个脚本 var script1 = CSharpScript. Create<int>("int x = 1;"). ContinueWith("int y = 2;"). ContinueWith("x + y"); Console.WriteLine("x + y : " + (await script1.RunAsync()).ReturnValue); //获取编译对象以访问所有Roslyn API //var compilation = script1.GetCompilation(); //从之前的状态继续执行脚本 var state1 = await CSharpScript.RunAsync("int x = 1;"); state1 = await state1.ContinueWithAsync("int y = 2;"); state1 = await state1.ContinueWithAsync("x+y"); Console.WriteLine("x + y : " + state1.ReturnValue); //读取代码文件并执行编译 var file = @"C:\Users\Administrator\source\repos\ConsoleApp2\ConsoleApp2/GenericGenerator.cs"; var originalText = File.ReadAllText(file); var syntaxTree = CSharpSyntaxTree.ParseText(originalText);//获取语法树 var type = CompileType("GenericGenerator", syntaxTree);//执行编译并获取类型 var transformer = Activator.CreateInstance(type); var newContent = (string)type.GetMethod("Transform").Invoke(transformer, new object[] { "某个泛型类的全文,假装我是泛型类 Walterlv<T> is a sb.", 2 }); var aa = new GenericGenerator(); var str = aa.Transform("某个泛型类的全文,假装我是泛型类 Walterlv<T> is a sb.", 25);
private static Type CompileType(string originalClassName, SyntaxTree syntaxTree) { // 指定编译选项。 var assemblyName = $"{originalClassName}.g"; var compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddReferences( // 这算是偷懒了吗?我把 .NET Core 运行时用到的那些引用都加入到引用了。 // 加入引用是必要的,不然连 object 类型都是没有的,肯定编译不通过。 AppDomain.CurrentDomain.GetAssemblies().Where(x=>!string.IsNullOrEmpty(x.Location)).Select(x => MetadataReference.CreateFromFile(x.Location))); // 编译到内存流中。 using (var ms = new MemoryStream()) { var result = compilation.Emit(ms); if (result.Success) { ms.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(ms.ToArray()); return assembly.GetTypes().First(x => x.Name == originalClassName); } throw new Exception(string.Join(Environment.NewLine, result.Diagnostics)); } }
当然还有这种四不像代码也是可以滴
//不仅可以在脚本引擎中引用命名空间,还可以在脚本中引用,当然前提是在脚本引擎中引用了相关程序集 using System; //没有 namespace Xxx { /* code */ } 的定义,直接在外面定义一个类 public class Point { public int X {get; set;} public int Y {get; set;} public Point(int x, int y) { X = x; Y = y; } public override string ToString() { return $"({X}, {Y})"; } } //变量没有定义在任何类或类的方法中,正常的 C# 代码一定会报错,但骚断腿的 CSharpScript.EvaluateAsync<TResult>(string code) 能正常执行 var random = new Random(); int a = random.Next(0, 1000); int b = random.Next(0, 1000); Func<int, int, int> plus = (l, r) => l + r; //最后这个表达式没有分号结尾,也没有使用变量接收结果。当然,最后这个带返回值的表达式的结果会在 string result = await CSharpScript.EvaluateAsync<string>(code); 的 result 里收到 $"{a}+{b}={plus(a, b)}\r\nPoint : {new Point(random.Next(0, 1000), random.Next(0, 1000))}"
ICSharpCode.Decompiler的最新版 6.0.1 预览版1支持最新框架的程序集反编译,同时支持了大多数C#特性,包括部分C# 8.0刚出的新特性。
现在的.Net Core真是可以玩出各种骚操作,太好玩了。当然,在下面的演示代码中有一个集成在网站项目的演示,下载后运行IdentityServer项目并访问/OnlineCode/CSharp
和/DeCompiler/CSharp
就可以查看效果。C#脚本大多数演示代码在CSharpScriptDemo项目中。
效果预览
脚本运行
反编译代码
转载请完整保留以下内容,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!
本文地址:https://www.cnblogs.com/coredx/p/12045104.html
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
本文作者:coredx
本文链接:https://www.cnblogs.com/coredx/p/12045104.html
版权声明:本作品采用署名-非商业性使用-禁止演绎 4.0 国际版 (CC BY-NC-ND 4.0)许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步