直接使用汇编编写 .NET Standard 库
前言
Common Language Runtime(CLR)是一个很强大的运行时,它接收 Common Intermediate Language(CIL) 的输入并最终产生机器代码并执行。CIL 在 CLR 上相当于 ASM 汇编代码的存在。
CLR 之上的语言 C#、F#、VB.NET 等语言的类型系统固然设计得不错,但是有的时候我们需要一些操作绕过类型系统的检查,或者有的时候语言本身并不能满足我们的需求。
需要使用 CIL 的常见场景:
- 我们需要绕过类型系统,在类型系统上面 “开洞”。
- 我们需要优化程序的性能,直接使用 CIL 编程可以如同使用汇编一样完全的控制程序的逻辑,对程序进行人肉优化。
- 直接利用 C#、F# 等语言编译成的 CIL 有其独特的模式,容易被反编译软件从 CIL 还原为源代码,而如果直接采用 CIL 编程则很容易避开编译器生成代码的固有模式,使得代码无需进行任何混淆即可让所有反编译器失效。
需要注意:CLR 的 JIT 部分优化依赖于 CIL 的特定模式,直接采用 CIL 进行编程而不利用 C# 等语言的编译器生成特定模式的 CIL 可能导致优化失效,如向量化、模式匹配缓存和常数时间优化等,因此在直接使用 CIL 进行编程时最好对 CLR 的 JIT 有一定了解,以规避潜在的性能问题,JIT 的源代码在 https://github.com/dotnet/runtime/tree/master/src/coreclr/src/jit。
准备工作
首先我们创建一个 .NET Standard 项目:
mkdir MyILProject
cd MyILProject
dotnet new classlib
然后创建 global.json
和 nuget.config
文件用于配置 SDK:
dotnet new global
dotnet new nuget
将 global.json
的内容修改为如下,添加 IL SDK 来源:
{
"msbuild-sdks": {
"Microsoft.NET.Sdk.IL": "3.0.0-preview-27318-01"
}
}
然后打开 nuget.config
,将内容修改如下,添加 .net core myget 源:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
</packageSources>
</configuration>
之前创建的为 C# 类库项目,但是我们此时需要的是 IL 类库项目,因此将 MyILProject.csproj
文件重命名为 MyILProject.ilproj
。
打开 MyILProject.ilproj
文件,引入 IL SDK,并添加一系列的属性(如:输出类型、优化选项、工具链等):
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.1</TargetFramework>
<DebugOptimization>IMPL</DebugOptimization>
<DebugOptimization Condition="'$(Configuration)' == 'Release'">OPT</DebugOptimization>
<MicrosoftNetCoreIlasmPackageVersion>3.0.0-preview-27318-01</MicrosoftNetCoreIlasmPackageVersion>
</PropertyGroup>
</Project>
至此,万事俱备
第一个文件
我们删除掉原有的 C# 代码文件 Class1.cs
,创建代码文件 Class1.il
,并添加以下 CIL 代码并保存:
.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!
,然后加载参数的值后返回该值。
测试代码
我们在上级目录创建一个测试项目试试:
cd ..
mkdir Test
cd Test
dotnet new console
dotnet add reference ../MyILProject
然后修改 Program.cs
:
using System;
using MyILProject;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Class1.Hello(25));
}
}
}
运行
dotnet run
可以看到输出为:
Hello world!
25
与我们所期望的一致。
然后我们试一下实例化 Class1
:
var x = new Class1();
却发现报错:
Program.cs(10,28): error CS1729: 'Class1' does not contain a constructor that takes 0 arguments [...\Test.csproj]
这是因为,我们没有为这个类创建构造方法,那么很简单,我们只需要加一个构造方法即可,要注意构造方法特有的方法名为 .ctor
:
.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
}
然后就可以成功调用了!
添加引用
你会发现一个问题,上述代码虽然能正常运行,但是编译的时候却存在警告:
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:
.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=...
):
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.1</TargetFramework>
<DebugOptimization>IMPL</DebugOptimization>
<DebugOptimization Condition="'$(Configuration)' == 'Release'">OPT</DebugOptimization>
<MicrosoftNetCoreIlasmPackageVersion>3.0.0-preview-27318-01</MicrosoftNetCoreIlasmPackageVersion>
<IlasmFlags>$(IlasmFlags) -INCLUDE=include</IlasmFlags>
</PropertyGroup>
</Project>
这次我们再次尝试编译,就不会报错了。
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.com/dotnet/runtime/blob/master/src/libraries/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.il
直接使用汇编编写 .NET Standard 库的更多相关文章
- (六)Net Core项目使用Controller之一 c# log4net 不输出日志 .NET Standard库引用导致的FileNotFoundException探究 获取json串里的某个属性值 common.js 如何调用common.js js 筛选数据 Join 具体用法
(六)Net Core项目使用Controller之一 一.简介 1.当前最流行的开发模式是前后端分离,Controller作为后端的核心输出,是开发人员使用最多的技术点. 2.个人所在的团队已经选择 ...
- c++调用自己编写的静态库(通过eclipse)
转:https://blog.csdn.net/hao5335156/article/details/80282829 参考:https://blog.csdn.net/u012707739/arti ...
- Delphi调用C# 编写dll动态库
Delphi调用C# 编写dll动态库 编写C#dll的方法都一样,首先在vs2005中创建一个“类库”项目WZPayDll, using System.Runtime.InteropServices ...
- Linux 下Python调用C++编写的动态库
在工程中用到使用Python调用C++编写的动态库,结果报如下错误: OSError: ./extract_str.so: undefined symbol: _ZNSt8ios_base4InitD ...
- C语言编写静态链接库及其使用
本篇讲述使用C语言编写静态链接库,而且使用C和C++的方式来调用等. 一.静态库程序:执行时不独立存在,链接到可执行文件或者动态库中,目标程序的归档. 1.用C编写静态库步骤 a.建立项目(Win32 ...
- 在Xamarin开发中,UWP环境下无法进入断点调试standard库的问题解决方案
环境如下 选择的代码共享方案为standard模式 再多平台依赖注入的时候,断点一直提示没有加载文档. 进入到目标平台项目Debug文件夹下,查看.发现standard库引用进来后,对应的*.pdb文 ...
- .NET Standard库引用导致的FileNotFoundException探究
微软近几年推出.NET Standard,将.NET Framework,.NET Core,Xamarin等目标平台的api进行标准化和统一化,极大地方便了类库编写人员的工作.简单的说,类库编写人员 ...
- 四、使用汇编编写LED裸机驱动
1. 确定硬件连接 打开OK6410底板电路图,找到LED,可以发现NLEDx为0时LED点亮. 找到LED的控制引脚,发现LED控制引脚通过连接器连到了核心板: 打开核心板电路图,找到对应的连接器中 ...
- composer的autoload来自动加载自己编写的函数库与类库?
1.使用命令composer init生成composer.json文件,并编辑autoload选项内容如下: 其中又包含主要的两个选项: files 和 psr-4. files就是需要compos ...
随机推荐
- Tomcat9乱码解决
在tomcat的解压目录下找到conf,打开进入,logging.properties文件,在该文件中,修改 java.util.logging.ConsoleHandler.encoding = U ...
- web api 的 安全 认证问题 , 对外开放 的 时候 需要考虑到安全的问题
关于 OWIN OAuth , web api的认证,全局验证, 安全方面的验证 有必要 去 自己捣鼓一下.
- windows10卸载虚拟机忘记按照步骤卸载的实际问题
好久没有写博客了,由于太多事情,工作需要用到虚拟机,结果,虚拟机出问题,,,怎么办???我的办法就是卸载了重新安装一个,结果呢?太心急没有按照不知操作,今天弄了一下午,终于弄好了... 错误原因,用了 ...
- Python+Flask+MysqL的web技术建站过程
1.个人学期总结 时间过得飞快,转眼间2017年就要过去.这一年,我学习JSP和Python,哪一门都像一样新的东西,之前从来没有学习过. 这里我就用我学习过的Python和大家分享一下,我是怎么从一 ...
- CSS-01-引入css的三种方法
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- mysql--->B+tree索引的设计原理
1.什么是数据库的索引 每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不 ...
- a标签跳转小程序
随着小程序应用的逐步广泛,与公众号之前的协同运营越来越重要,通过公众号的各个入口为小程序导流.最近在运营中发现,大家对公众号关注后回复.关键词回复,超链接小程序的需求. 首先把小程序绑定 ...
- 在vue中使用jquery
首先默认你已经有了一个vue程序,如果你想在vue中使用jquery,那么请继续阅读. 当然,加入你没有一个vue程序,这里也给出创建一个vue程序的命令.当然,你肯定装了vue-cli,不然你不会点 ...
- Tomcat异常:UnsupportedClassVersionError unsupported major.minor version 51.0 unable to load class [dup
案例 今天把项目换成了jdk1.8,启动tomcat报如下异常: UnsupportedClassVersionError unsupported major.minor version 51.0 u ...
- 实验12: OSPF
实验9-1:单区域点到点链路的OSPF 实验目的通过本实验可以掌握:(1)在路由器上启动OSPF 路由进程(2)启用参与路由协议的接口,并且通告网络及所在的区域(3)度量值cost 的计算(4)点到点 ...