.NET7的AOT的使用
背景
其实,规划这篇文章有一段时间了,但是比较懒,所以一直拖着没写。
最近时总更新太快了,太卷了,所以借着 .NET 7 正式版发布,熬夜写完这篇文章,希望能够追上时总的一点距离。
本文主要介绍如何在 .NET 和 Go 语言中如何生成系统(Windows)动态链接库,又如何从代码中引用这些库中的函数。
在 .NET 部分,介绍如何使用 AOT、减少二进制文件大小、使用最新的 [LibraryImport] 导入库函数;
在 Go 语言部分,介绍如何使用 GCC 编译 Go 代码、如何通过 syscall 导入库函数。
在文章中会演示 .NET 和 Go 相互调用各自生成的动态链接库,以及对比两者之间的差异。
C# 部分
环境要求
SDK:.NET 7 SDK、Desktop development with C++ workload。
IDE:Visual Studio 2022
Desktop development with C++ workload 是一个工具集,里面包含 C++ 开发工具,需要在 Visual Studio Installer 中安装,如下图红框中所示。

创建一个控制台项目
首先创建一个 .NET 7 控制台项目,名称为 CsharpAot。
打开项目之后,基本代码如图所示:

我们使用下面的代码做测试:
publicclassProgram
{staticvoidMain(){Console.WriteLine("C# Aot!");Console.ReadKey();}
}体验 AOT 编译
这一步,可以参考官方网站的更多说明:
为了能够让项目发布时使用 AOT 模式,需要在项目文件中加上 <PublishAot>true</PublishAot> 选项。

然后使用 Visual Studio 发布项目。
发布项目的配置文件设置,需要按照下图进行配置。

AOT 跟 生成单个文件 两个选项不能同时使用,因为 AOT 本身就是单个文件。
配置完成后,点击 发布,然后打开 Release 目录,会看到如图所示的文件。

.exe 是独立的可执行文件,不需要再依赖 .NET Runtime 环境,这个程序可以放到其他没有安装 .NET 环境的机器中运行。
然后删除以下三个文件:
CsharpAot.expCsharpAot.libCsharpAot.pdb光用 .exe 即可运行,其他是调试符号等文件,不是必需的。
剩下 CsharpAot.exe 文件后,启动这个程序:

C# 调用库函数
这一部分的代码示例,是从笔者的一个开源项目中抽取出来的,这个项目封装了一些获取系统资源的接口,以及快速接入 Prometheus 监控。
因为后续代码需要,所以现在请开启 “允许不安全代码”。
本小节的示例是通过使用 kernel32.dll 去调用 Windows 的内核 API(Win32 API),调用 GlobalMemoryStatusEx 函数 检索有关系统当前使用物理内存和虚拟内存的信息。
使用到的 Win32 函数
关于 .NET 调用动态链接库的方式,在 .NET 7 之前,通过这样调用:
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]internalstaticextern Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer);在 .NET 7 中,出现了新的操作方式 [LibraryImport]。
文档是这样介绍的:
Indicates that a source generator should create a functionfor marshalling arguments instead of relying on the runtime to generate an equivalent marshalling function at run time.指示源生成器应创建用于编组参数的函数,而不是依赖运行库在运行时生成等效的编组函数。简单来说,就是我们要使用 AOT 写代码,然后代码中引用到别的动态链接库时,需要使用 [LibraryImport] 引入这些函数。
笔者没有在 AOT 下测试过 [DllImport],读者感兴趣可以试试。
新建两个结构体 MEMORYSTATUS.cs、MemoryStatusExE.cs 。
MEMORYSTATUS.cs :
publicstruct MEMORYSTATUS
{internal UInt32 dwLength;internal UInt32 dwMemoryLoad;internal UInt32 dwTotalPhys;internal UInt32 dwAvailPhys;internal UInt32 dwTotalPageFile;internal UInt32 dwAvailPageFile;internal UInt32 dwTotalVirtual;internal UInt32 dwAvailVirtual;
}MemoryStatusExE.cs :
publicstruct MemoryStatusExE
{///<summary>/// 结构的大小,以字节为单位,必须在调用 GlobalMemoryStatusEx 之前设置此成员,可以用 Init 方法提前处理///</summary>///<remarks>应当使用本对象提供的 Init ,而不是使用构造函数!</remarks>internal UInt32 dwLength;///<summary>/// 一个介于 0 和 100 之间的数字,用于指定正在使用的物理内存的大致百分比(0 表示没有内存使用,100 表示内存已满)。///</summary>internal UInt32 dwMemoryLoad;///<summary>/// 实际物理内存量,以字节为单位///</summary>internal UInt64 ullTotalPhys;///<summary>/// 当前可用的物理内存量,以字节为单位。这是可以立即重用而无需先将其内容写入磁盘的物理内存量。它是备用列表、空闲列表和零列表的大小之和///</summary>internal UInt64 ullAvailPhys;///<summary>/// 系统或当前进程的当前已提交内存限制,以字节为单位,以较小者为准。要获得系统范围的承诺内存限制,请调用GetPerformanceInfo///</summary>internal UInt64 ullTotalPageFile;///<summary>/// 当前进程可以提交的最大内存量,以字节为单位。该值等于或小于系统范围的可用提交值。要计算整个系统的可承诺值,调用GetPerformanceInfo核减价值CommitTotal从价值CommitLimit///</summary>internal UInt64 ullAvailPageFile;///<summary>/// 调用进程的虚拟地址空间的用户模式部分的大小,以字节为单位。该值取决于进程类型、处理器类型和操作系统的配置。例如,对于 x86 处理器上的大多数 32 位进程,此值约为 2 GB,对于在启用4 GB 调整的系统上运行的具有大地址感知能力的 32 位进程约为 3 GB 。///</summary>internal UInt64 ullTotalVirtual;///<summary>/// 当前在调用进程的虚拟地址空间的用户模式部分中未保留和未提交的内存量,以字节为单位///</summary>internal UInt64 ullAvailVirtual;///<summary>/// 预订的。该值始终为 0///</summary>internal UInt64 ullAvailExtendedVirtual;internalvoidRefresh(){dwLength = checked((UInt32)Marshal.SizeOf(typeof(MemoryStatusExE)));}
}定义引用库函数的入口:
publicstaticpartialclassNative
{///<summary>/// 检索有关系统当前使用物理和虚拟内存的信息///</summary>///<param name="lpBuffer"></param>///<returns></returns>[LibraryImport("Kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]internalstaticpartial Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer);
}然后调用 Kernel32.dll 中的函数:
publicclassProgram
{staticvoidMain(){var result = GetValue();Console.WriteLine($"当前实际可用内存量:{result.ullAvailPhys / 1000 / 1000}MB");Console.ReadKey();}///<exception cref="Win32Exception"></exception>publicstatic MemoryStatusExE GetValue(){var memoryStatusEx = new MemoryStatusExE();// 重新初始化结构的大小memoryStatusEx.Refresh();// 刷新值if (!Native.GlobalMemoryStatusEx(ref memoryStatusEx)) thrownew Win32Exception("无法获得内存信息");return memoryStatusEx;}
}使用 AOT 发布项目,执行 CsharpAot.exe 文件。

减少体积
在前面两个例子中可以看到 CsharpAot.exe 文件大约在 3MB 左右,但是这个文件还是太大了,那么我们如何进一步减少 AOT 文件的大小呢?
需要注意的是,裁剪是没有那么简单的,里面配置繁多,有一些选项不能同时使用,每个选项又能带来什么样的效果,这些选项可能会让开发者用得很迷茫。
经过笔者的大量测试,笔者选用了以下一些配置,能够达到很好的裁剪效果,供读者测试。
首先,引入一个库:
<ItemGroup><PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" /></ItemGroup>接着,在项目文件中加入以下选项:
<!--AOT 相关--><PublishAot>true</PublishAot> <TrimMode>full</TrimMode><RunAOTCompilation>True</RunAOTCompilation><PublishTrimmed>true</PublishTrimmed><TrimmerRemoveSymbols>true</TrimmerRemoveSymbols><PublishReadyToRunEmitSymbols>false</PublishReadyToRunEmitSymbols><DebuggerSupport>false</DebuggerSupport><EnableUnsafeUTF7Encoding>true</EnableUnsafeUTF7Encoding><InvariantGlobalization>true</InvariantGlobalization><HttpActivityPropagationSupport>false</HttpActivityPropagationSupport><MetadataUpdaterSupport>true</MetadataUpdaterSupport><UseSystemResourceKeys>true</UseSystemResourceKeys><IlcDisableReflection >true</IlcDisableReflection>最后,发布项目。
吃惊!生成的可执行文件只有 1MB 了,而且还可以正常执行。

笔者注:虽然现在看起来 AOT 的文件很小了,但是如果使用到 HttpClient、System.Text.Json 等库,哪怕只用到了一两个函数,最终包含这些库以及这些库使用到的依赖,生成的 AOT 文件会大得惊人。
所以,如果项目中使用到其他 nuget 包的时候,别想着生成的 AOT 能小多少!
C# 导出函数
在 C 语言中,导出一个函数的格式可以这样:
// MyCFuncs.h#ifdef __cplusplusextern"C" { // only need to export C interface if// used by C++ source code#endif__declspec( dllimport ) voidMyCFunc();
__declspec( dllimport ) voidAnotherCFunc();#ifdef __cplusplus
}
#endif当代码编译之后,我们就可以通过引用生成的库文件,调用 MyCFunc、AnotherCFunc 两个方法。
如果不导出的话,别的程序是无法调用库文件里面的函数。
因为 .NET 7 的 AOT 做了很多改进,因此,.NET 程序也可以导出函数了。
新建一个项目,名字就叫 CsharpExport 吧,我们接下来就在这里项目中编写我们的动态链接库。
添加一个 CsharpExport.cs 文件,内容如下:
using System.Runtime.InteropServices;namespaceCsharpExport
{publicclassExport{[UnmanagedCallersOnly(EntryPoint = "Add")]publicstaticintAdd(int a, int b){return a + b;}}
}然后在 .csproj 文件中,加上 PublishAot 选项。

然后通过以下命令发布项目,生成链接库:
dotnet publish -p:NativeLib=Shared -r win-x64 -c Release
看起来还是比较大,为了继续裁剪体积,我们可以在 CsharpExport.csproj 中加入以下配置,以便生成更小的可执行文件。
<!--AOT 相关--><PublishAot>true</PublishAot><TrimMode>full</TrimMode><RunAOTCompilation>True</RunAOTCompilation><PublishTrimmed>true</PublishTrimmed><TrimmerRemoveSymbols>true</TrimmerRemoveSymbols><PublishReadyToRunEmitSymbols>false</PublishReadyToRunEmitSymbols><DebuggerSupport>false</DebuggerSupport><EnableUnsafeUTF7Encoding>true</EnableUnsafeUTF7Encoding><InvariantGlobalization>true</InvariantGlobalization><HttpActivityPropagationSupport>false</HttpActivityPropagationSupport><MetadataUpdaterSupport>true</MetadataUpdaterSupport><UseSystemResourceKeys>true</UseSystemResourceKeys><IlcDisableReflection >true</IlcDisableReflection>
C# 调用 C# 生成的 AOT
在本小节中,将使用 CsharpAot 项目调用 CsharpExport 生成的动态链接库。
把 CsharpExport.dll 复制到 CsharpAot 项目中,并配置 始终复制。

在 CsharpAot 的 Native 中加上:
[LibraryImport("CsharpExport.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.I4)]internalstaticpartial Int32 Add(Int32 a, Int32 b);
然后在代码中使用:
staticvoidMain(){var result = Native.Add(1, 2);Console.WriteLine($"1 + 2 = {result}");Console.ReadKey();}在 Visual Studio 里启动 Debug 调试:

可以看到,是正常运行的。
接着,将 CsharpAot 项目发布为 AOT 后,再次执行:

可以看到,.NET AOT 调用 .NET AOT 的代码是没有问题的。
相关文章:
.NET7的AOT的使用
背景其实,规划这篇文章有一段时间了,但是比较懒,所以一直拖着没写。最近时总更新太快了,太卷了,所以借着 .NET 7 正式版发布,熬夜写完这篇文章,希望能够追上时总的一点距离。本文主要介绍如何在…...
分布式缓存的问题
1,Redis缓存穿透问题 Redis缓存穿透问题是指查询一个一定不存在的数据,由于这样的数据缓存一定不命中,所以这样的请求一定会打到数据库上。但是由于数据库里面也没有这样数据,且也没有将这样的null值缓存到数据库,从而造成这样的…...
golang入门笔记——内存管理和编译器优化
静态分析 静态分析:不执行程序代码,推导程序的行为,分析程序的性质 控制流(control flow):程序的执行流程 数据流(data flow):数据在控制流上的传递 通过分析控制流和…...
GEE学习笔记 七十:【GEE之Python版教程四】Python基础编程二
通过上一章的讲解,我们对于python有了初步的了解,这一章就详细讲解一下python的各个变量以及运算规则等内容。 关于测试代码推荐初学者将每一段代码都自己敲入编辑器中在本地运行。 1、数值 这是任何编程中都会有的基本变量,在python支持的…...
股票投资新出发之知识体系构建导论
文章目录前言参考资料如何构建体系实践理论tips前言 自2021年股票开户,投资已有2年左右,但更多的是凭感觉式的拍脑袋投资,没有自己的投资体系,所以开此专栏从零开始构建知识体系,勉励自己不断学习。两年的投资经验让我…...
蓝桥杯算法训练合集 十六 1.首字母变大写2.盾神计科导作业3.Cinema4.接水问题
目录 1.首字母变大写 2.盾神计科导作业 3.Cinema 4.接水问题 1.首字母变大写 问题描述 对一个字符串中的所有单词,如果单词的首字母不是大写字母,则把单词的首字母变成大写字母。在字符串中,单词之间通过空白符分隔,空白符包括…...
密码的世界
网络世界中常见的攻击方法 窃听攻击 窃听攻击是网络世界最常见的一种攻击方式,一些不能泄露的隐私信息,例如银行卡密码,账号密码,如果被窃听泄露的话通常会带来比较严重的后果。 中间人攻击 在中间人攻击中,小明准…...
如何用一句话感动测试工程师?产品和技术都这么说!
测试工程师在公司里的地位一言难尽,产品挥斥苍穹,指引产品前路;开发编写代码实现功能,给产品带来瞩目成就。两者,一个是领航员,一个是开拓者,都是聚光灯照耀的对象,唯独团队中的保障…...
MySQL中使用索引优化
目录 一.使用索引优化 数据准备 避免索引失效应用-全值匹配 避免索引失效应用-最左前缀法则 避免索引失效应用-其他匹配原则 1、 2、 3、 4、 5、 一.使用索引优化 索引是数据库优化最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的MySQL的性能优化…...
Linux C/C++ 多线程TCP/UDP服务器 (监控系统状态)
Linux环境中实现并发TCP/IP服务器。多线程在解决方案中提供了并发性。由于并发性,它允许多个客户端同时连接到服务器并与服务器交互。 Linux多线程编程概述 许多应用程序同时处理多项杂务。服务器应用程序处理并发客户端;交互式应用程序通常在处理后台…...
【JavaScript】JavaScript基本使用方法
如何回复程序员发来的短信:Hello world —hello nerd. 前言: 大家好,我是程序猿爱打拳。今天我给大家讲解的是初识JavaScript中基本组成成分、引入方法、输入输出语句,并用源码与效果图的方式展示给大家。 目录 1.JavaScript组成…...
Python数据容器、list列表、tuple元组、str字符串、数据容器(序列)切片、set集合、dict字典、字符串大小比较
数据来源 01 数据容器 为什么学习数据容器 数据容器 总结 02 列表 1)列表定义 为什么需要列表 列表的定义语法 列表的定义方式 演示 """ 演示数据容器之:list列表 语法:[元素,元素,......] """ # 定义一个列表list my_list …...
Python urllib
Python urllib Python urllib 库用于操作网页 URL,并对网页的内容进行抓取处理。 本文主要介绍 Python3 的 urllib。 urllib 包 包含以下几个模块: urllib.request - 打开和读取 URL。urllib.error - 包含 urllib.request 抛出的异常。urllib.parse …...
Centos7安装Python3
前言系统版本:Centos7.6python版本: python 3.10.4下载python下载链接:直通车找到对应版本的python安装包,这里以python 3.10.4为例点击3.10.4版本的链接,拉到最下面找到Files中对应的linux安装包鼠标右键复制下载链接登录linux系…...
[U3D ShaderGraph] 全面学习ShaderGraph节点 | 第四课 | Input/Lighting
📣📣📣本专栏所有内容在完结之前全部为试读模式,专栏完结之后会取消试读模式,如果感觉内容还不错的话请支持一下📣📣📣 ShaderGraph是可视化的着色器编辑工具。您可以使用此工具以可视方式创建着色器。 本专栏可以让你更了解ShaderGraph中每个节点的功能,更自如的…...
SpringBoot升级到3.0
SpringBoot 3.0出来有一段时间了,一直没时间来整理,这次来看一下吧。 Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可以“直接运行”。 SpringBoot升级到3.01. SpringBoot的维护时间线2. pom添加3. 打包大小对比4. 升…...
JavaWeb8-线程安全问题
目录 1.概念 1.1.单线程 1.2.多线程 2.导致线程不安全的5个因素 ①抢占式执行(首要原因) ②多个线程同时修改了同一个变量 ③非原子性操作 ④内存可见性 ⑤指令重排序 线程优点:加速程序性能。线程缺点:存在安全问题。 1…...
进程切换-
实验课之前有一些问题 中断机制 第一个问题: interrupt的两个状态源头: 外中断多由随机中断(异步中断)造成,如鼠标点击,键盘输入; 内终端多由故障终端:程序运行异常,硬件…...
python--matplotlib(2)
前言 Matplotlib画图工具的官网地址是 http://matplotlib.org/ Python环境下实现Matlab制图功能的第三方库,需要numpy库的支持,支持用户方便设计出二维、三维数据的图形显示,制作的图形达到出版级的标准。 实验环境 Pycharm2020.2.5社区版,w…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
