.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…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...
虚幻基础:角色旋转
能帮到你的话,就给个赞吧 😘 文章目录 移动组件使用控制器所需旋转:组件 使用 控制器旋转将旋转朝向运动:组件 使用 移动方向旋转 控制器旋转和移动旋转 缺点移动旋转:必须移动才能旋转,不移动不旋转控制器…...
