.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…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
