直接使用汇编编写 .NET Standard 库

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

直接使用汇编编写 .NET Standard 库

hez2010   2020-02-27 我要评论
# 前言 Common Language Runtime(CLR)是一个很强大的运行时,它接收 Common Intermediate Language(CIL) 的输入并最终产生机器代码并执行。CIL 在 CLR 上相当于 ASM 汇编代码的存在。 CLR 之上的语言 C#、F#、VB.NET 等语言的类型系统固然设计得不错,但是有的时候我们需要一些操作绕过类型系统的检查,或者有的时候语言本身并不能满足我们的需求。 需要使用 CIL 的常见场景: 1. 我们需要绕过类型系统,在类型系统上面 “开洞”。 2. 我们需要优化程序的性能,直接使用 CIL 编程可以如同使用汇编一样完全的控制程序的逻辑,对程序进行人肉优化。 3. 直接利用 C#、F# 等语言编译成的 CIL 有其独特的模式,容易被反编译软件从 CIL 还原为源代码,而如果直接采用 CIL 编程则很容易避开编译器生成代码的固有模式,使得代码无需进行任何混淆即可让所有反编译器失效。 需要注意:CLR 的 JIT 部分优化依赖于 CIL 的特定模式,直接采用 CIL 进行编程而不利用 C# 等语言的编译器生成特定模式的 CIL 可能导致优化失效,如向量化、模式匹配缓存和常数时间优化等,因此在直接使用 CIL 进行编程时最好对 CLR 的 JIT 有一定了解,以规避潜在的性能问题,JIT 的源代码在 https://github.comhttps://img.qb5200.com/download-x/dotnet/runtime/tree/master/src/coreclr/src/jit。 # 准备工作 首先我们创建一个 .NET Standard 项目: ```bash mkdir MyILProject cd MyILProject dotnet new classlib ``` 然后创建 `global.json` 和 `nuget.config` 文件用于配置 SDK: ```bash dotnet new global dotnet new nuget ``` 将 `global.json` 的内容修改为如下,添加 IL SDK 来源: ```json { "msbuild-sdks": { "Microsoft.NET.Sdk.IL": "3.0.0-preview-27318-01" } } ``` 然后打开 `nuget.config`,将内容修改如下,添加 .net core myget 源: ```xml <?xml version="1.0" encoding="utf-8"?> ``` 之前创建的为 C# 类库项目,但是我们此时需要的是 IL 类库项目,因此将 `MyILProject.csproj` 文件重命名为 `MyILProject.ilproj`。 打开 `MyILProject.ilproj` 文件,引入 IL SDK,并添加一系列的属性(如:输出类型、优化选项、工具链等): ```xml Library netstandard2.1 IMPL OPT 3.0.0-preview-27318-01 ``` 至此,万事俱备 # 第一个文件 我们删除掉原有的 C# 代码文件 `Class1.cs`,创建代码文件 `Class1.il`,并添加以下 CIL 代码并保存: ```asm .assembly MyILProject { .ver 1:0:0:0 } .module MyILProject.dll .class public auto ansi sealed MyILProject.Class1 extends [System]System.Object { .method public hidebysig static int32 Hello(int32) cil managed { .maxstack 4 ldstr "Hello World!" call void [System.Console]System.Console::WriteLine(string) ldarg.0 ret } } ``` 以上代码中,`.assembly` 标识了程序集名称,`.module` 标识了模块名称,一般来说这两个名字和项目名称保持一致。 然后我们创建了一个 `class` `Class1`,位于 `MyILProject` 这个 `namespace` 下,该 `class` 为 `public sealed` 的,且继承自 `System.Object`。 最后我们添加了一个静态方法 `int Hello(int)`,该方法调用 `System.Console.WriteLine` 输出字符串 `Hello world!`,然后加载参数的值后返回该值。 # 测试代码 我们在上级目录创建一个测试项目试试: ```bash cd .. mkdir Test cd Test dotnet new console dotnet add reference ../MyILProject ``` 然后修改 `Program.cs`: ```csharp using System; using MyILProject; namespace Test { class Program { static void Main(string[] args) { Console.WriteLine(Class1.Hello(25)); } } } ``` 运行 ```bash dotnet run ``` 可以看到输出为: ``` Hello world! 25 ``` 与我们所期望的一致。 然后我们试一下实例化 `Class1`: ```csharp var x = new Class1(); ``` 却发现报错: ```log Program.cs(10,28): error CS1729: 'Class1' does not contain a constructor that takes 0 arguments [...\Test.csproj] ``` 这是因为,我们没有为这个类创建构造方法,那么很简单,我们只需要加一个构造方法即可,要注意构造方法特有的方法名为 `.ctor`: ```asm .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { .maxstack 8 ldarg.0 call instance void [System.Private.CoreLib]System.Object::.ctor() nop ret } ``` 然后就可以成功调用了! # 添加引用 你会发现一个问题,上述代码虽然能正常运行,但是编译的时候却存在警告: ```log Class.il(9): warning : Reference to undeclared extern assembly 'mscorlib'. Attempting autodetect [...\MyILProject.ilproj] Class.il(15): warning : Reference to undeclared extern assembly 'System.Console'. Attempting autodetect [...\MyILProject.ilproj] Class.il(26): warning : Reference to undeclared extern assembly 'System.Private.CoreLib'. Attempting autodetect [...\MyILProject.ilproj] ``` 这是因为我们并没有声明我们引入的库 `mscorlib`,`System.Console` 和 `System.Provate.CoreLib`,所幸的是,因为这些是 .NET Core SDK 中自带的库因此编译器可以自动查找并补上引用,所以没有报错,否则运行的时候会抛出异常: `System.IO.FileNotFoundException: Could not load file or assembly xxxxx` 如果想消除这些警告,我们可以创建一个头文件引用这些库,然后在 CIL 代码文件的头部 `#include` 头文件,示例如下: 在 `MyILProject` 中新建 `include` 文件夹,创建一个 include.h: ```cpp .assembly extern System.Runtime { .publickeytoken = ( B0 3F 5F 7F 11 D5 0A 3A ) .ver 4:0:0:0 } .assembly extern System.Console { .publickeytoken = ( B0 3F 5F 7F 11 D5 0A 3A ) .ver 4:0:0:0 } .assembly extern System.Private.CoreLib { .publickeytoken = ( B0 3F 5F 7F 11 D5 0A 3A ) .ver 4:0:0:0 } ``` 然后在 `Class1.il` 头部加一行 `#include "include.h"` 包含该文件。 最后修改 `MyILProject.ilproj`,将 `include` 文件夹加入 `INCLUDE` 查找目录(`-INCLUDE=...`): ```xml Library netstandard2.1 IMPL OPT 3.0.0-preview-27318-01 $(IlasmFlags) -INCLUDE=include\netstandard ``` 这次我们再次尝试编译,就不会报错了。 # CLI 上面的内容只简单的使用了一些 CIL 语法,然而 CIL 也是非常强大的,包含有很多内容,具体可以参考 Common Language Infrastructure(CLI),这部分的内容包含在标准 ECMA-355 中,截至本文发布,最新的 CLI 标准版本是 2012 年发布的第六版。 ECMA-355:https://www.ecma-international.org/publications/standards/Ecma-335.htm ,欢迎各位读者前去阅读。 # 应用案例 .NET BCL 中提供了一个特殊的库:`System.Runtime.CompilerServices.Unsafe`,这个库允许你无视 C# 的类型系统进行各种类型转换等的骚操作,这是你用 C# 无论如何都不可能写出来的,官方也知道这一点,因此该库完全是直接使用 CIL 编写的,源代码可参考:https://github.comhttps://img.qb5200.com/download-x/dotnet/runtime/blob/master/src/libraries/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.il

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们