最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

.NET 7 AOT 的使用方法

2023-02-19 21:40 作者:吳小敏63  | 我要投稿

背景

其實(shí),規(guī)劃這篇文章有一段時(shí)間了,但是比較懶,所以一直拖著沒寫。

最近時(shí)總更新太快了,太卷了,所以借著 .NET 7 正式版發(fā)布,熬夜寫完這篇文章,希望能夠追上時(shí)總的一點(diǎn)距離。

本文主要介紹如何在 .NET 和 Go 語(yǔ)言中如何生成系統(tǒng)(Windows)動(dòng)態(tài)鏈接庫(kù),又如何從代碼中引用這些庫(kù)中的函數(shù)。

在 .NET 部分,介紹如何使用 AOT、減少二進(jìn)制文件大小、使用最新的?[LibraryImport]?導(dǎo)入庫(kù)函數(shù);

在 Go 語(yǔ)言部分,介紹如何使用 GCC 編譯 Go 代碼、如何通過(guò)?syscall?導(dǎo)入庫(kù)函數(shù)。

在文章中會(huì)演示 .NET 和 Go 相互調(diào)用各自生成的動(dòng)態(tài)鏈接庫(kù),以及對(duì)比兩者之間的差異。

本文文章內(nèi)容以及源代碼,可以?https://github.com/whuanle/csharp_aot_golang?中找到,如果本文可以給你帶來(lái)幫助,可以到 Github 點(diǎn)個(gè)星星嘛。

C# 部分

環(huán)境要求

SDK:.NET 7 SDKDesktop development with C++ workload。

IDE:Visual Studio 2022

Desktop development with C++ workload?是一個(gè)工具集,里面包含 C++ 開發(fā)工具,需要在?Visual Studio Installer?中安裝,如下圖紅框中所示。

創(chuàng)建一個(gè)控制臺(tái)項(xiàng)目

首先創(chuàng)建一個(gè) .NET 7 控制臺(tái)項(xiàng)目,名稱為?CsharpAot

打開項(xiàng)目之后,基本代碼如圖所示:

我們使用下面的代碼做測(cè)試:

public class Program{ ? ?static void Main() ? ?{ ? ? ? ?Console.WriteLine("C# Aot!"); ? ? ? ?Console.ReadKey(); ? ?} }

體驗(yàn) AOT 編譯

這一步,可以參考官方網(wǎng)站的更多說(shuō)明:

https://learn.microsoft.com/zh-cn/dotnet/core/deploying/native-aot/

為了能夠讓項(xiàng)目發(fā)布時(shí)使用 AOT 模式,需要在項(xiàng)目文件中加上?<PublishAot>true</PublishAot>?選項(xiàng)。

然后使用 Visual Studio 發(fā)布項(xiàng)目。

發(fā)布項(xiàng)目的配置文件設(shè)置,需要按照下圖進(jìn)行配置。

AOT 跟?生成單個(gè)文件?兩個(gè)選項(xiàng)不能同時(shí)使用,因?yàn)?AOT 本身就是單個(gè)文件。

配置完成后,點(diǎn)擊?發(fā)布,然后打開?Release?目錄,會(huì)看到如圖所示的文件。

.exe?是獨(dú)立的可執(zhí)行文件,不需要再依賴?.NET Runtime?環(huán)境,這個(gè)程序可以放到其他沒有安裝 .NET 環(huán)境的機(jī)器中運(yùn)行。

然后刪除以下三個(gè)文件:

? ?CsharpAot.exp ? ?CsharpAot.lib ? ?CsharpAot.pdb

光用?.exe?即可運(yùn)行,其他是調(diào)試符號(hào)等文件,不是必需的。

剩下?CsharpAot.exe?文件后,啟動(dòng)這個(gè)程序:

C# 調(diào)用庫(kù)函數(shù)

這一部分的代碼示例,是從筆者的一個(gè)開源項(xiàng)目中抽取出來(lái)的,這個(gè)項(xiàng)目封裝了一些獲取系統(tǒng)資源的接口,以及快速接入 Prometheus 監(jiān)控。

不過(guò)很久沒有更新了,最近沒啥動(dòng)力更新,讀者可以點(diǎn)擊這里了解一下這個(gè)項(xiàng)目:

https://github.com/whuanle/CZGL.SystemInfo/tree/net6.0/src/CZGL.SystemInfo/Memory

因?yàn)楹罄m(xù)代碼需要,所以現(xiàn)在請(qǐng)開啟 “允許不安全代碼”。

本小節(jié)的示例是通過(guò)使用?kernel32.dll?去調(diào)用 Windows 的內(nèi)核 API(Win32 API),調(diào)用?GlobalMemoryStatusEx?函數(shù)?檢索有關(guān)系統(tǒng)當(dāng)前使用物理內(nèi)存和虛擬內(nèi)存的信息。

使用到的 Win32 函數(shù)可參考:https://learn.microsoft.com/zh-cn/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex

關(guān)于 .NET 調(diào)用動(dòng)態(tài)鏈接庫(kù)的方式,在 .NET 7 之前,通過(guò)這樣調(diào)用:

? ?[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] ? ?[return: MarshalAs(UnmanagedType.Bool)] ? ?internal static extern Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer);

在 .NET 7 中,出現(xiàn)了新的操作方式?[LibraryImport]

文檔是這樣介紹的:

Indicates that a source generator should create a function for marshalling arguments instead of relying on the runtime to generate an equivalent marshalling function at run time. 指示源生成器應(yīng)創(chuàng)建用于編組參數(shù)的函數(shù),而不是依賴運(yùn)行庫(kù)在運(yùn)行時(shí)生成等效的編組函數(shù)。

簡(jiǎn)單來(lái)說(shuō),就是我們要使用 AOT 寫代碼,然后代碼中引用到別的動(dòng)態(tài)鏈接庫(kù)時(shí),需要使用?[LibraryImport]?引入這些函數(shù)。

筆者沒有在 AOT 下測(cè)試過(guò)?[DllImport],讀者感興趣可以試試。

新建兩個(gè)結(jié)構(gòu)體?MEMORYSTATUS.csMemoryStatusExE.cs?。

MEMORYSTATUS.cs?:

public struct 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?:

public struct MemoryStatusExE { ? ?/// <summary> ? ?/// 結(jié)構(gòu)的大小,以字節(jié)為單位,必須在調(diào)用 GlobalMemoryStatusEx 之前設(shè)置此成員,可以用 Init 方法提前處理 ? ?/// </summary> ? ?/// <remarks>應(yīng)當(dāng)使用本對(duì)象提供的 Init ,而不是使用構(gòu)造函數(shù)!</remarks> ? ?internal UInt32 dwLength; ? ?/// <summary> ? ?/// 一個(gè)介于 0 和 100 之間的數(shù)字,用于指定正在使用的物理內(nèi)存的大致百分比(0 表示沒有內(nèi)存使用,100 表示內(nèi)存已滿)。 ? ?/// </summary> ? ?internal UInt32 dwMemoryLoad; ? ?/// <summary> ? ?/// 實(shí)際物理內(nèi)存量,以字節(jié)為單位 ? ?/// </summary> ? ?internal UInt64 ullTotalPhys; ? ?/// <summary> ? ?/// 當(dāng)前可用的物理內(nèi)存量,以字節(jié)為單位。這是可以立即重用而無(wú)需先將其內(nèi)容寫入磁盤的物理內(nèi)存量。它是備用列表、空閑列表和零列表的大小之和 ? ?/// </summary> ? ?internal UInt64 ullAvailPhys; ? ?/// <summary> ? ?/// 系統(tǒng)或當(dāng)前進(jìn)程的當(dāng)前已提交內(nèi)存限制,以字節(jié)為單位,以較小者為準(zhǔn)。要獲得系統(tǒng)范圍的承諾內(nèi)存限制,請(qǐng)調(diào)用GetPerformanceInfo ? ?/// </summary> ? ?internal UInt64 ullTotalPageFile; ? ?/// <summary> ? ?/// 當(dāng)前進(jìn)程可以提交的最大內(nèi)存量,以字節(jié)為單位。該值等于或小于系統(tǒng)范圍的可用提交值。要計(jì)算整個(gè)系統(tǒng)的可承諾值,調(diào)用GetPerformanceInfo核減價(jià)值CommitTotal從價(jià)值CommitLimit ? ?/// </summary> ? ?internal UInt64 ullAvailPageFile; ? ?/// <summary> ? ?/// 調(diào)用進(jìn)程的虛擬地址空間的用戶模式部分的大小,以字節(jié)為單位。該值取決于進(jìn)程類型、處理器類型和操作系統(tǒng)的配置。例如,對(duì)于 x86 處理器上的大多數(shù) 32 位進(jìn)程,此值約為 2 GB,對(duì)于在啟用4 GB 調(diào)整的系統(tǒng)上運(yùn)行的具有大地址感知能力的 32 位進(jìn)程約為 3 GB 。 ? ?/// </summary> ? ?internal UInt64 ullTotalVirtual; ? ?/// <summary> ? ?/// 當(dāng)前在調(diào)用進(jìn)程的虛擬地址空間的用戶模式部分中未保留和未提交的內(nèi)存量,以字節(jié)為單位 ? ?/// </summary> ? ?internal UInt64 ullAvailVirtual; ? ?/// <summary> ? ?/// 預(yù)訂的。該值始終為 0 ? ?/// </summary> ? ?internal UInt64 ullAvailExtendedVirtual; ? ?internal void Refresh() ? ?{ ? ? ? ?dwLength = checked((UInt32)Marshal.SizeOf(typeof(MemoryStatusExE))); ? ?} }

定義引用庫(kù)函數(shù)的入口:

public static partial class Native{ ? ?/// <summary> ? ?/// 檢索有關(guān)系統(tǒng)當(dāng)前使用物理和虛擬內(nèi)存的信息 ? ?/// </summary> ? ?/// <param name="lpBuffer"></param> ? ?/// <returns></returns> ? ?[LibraryImport("Kernel32.dll", SetLastError = true)] ? ?[return: MarshalAs(UnmanagedType.Bool)] ? ?internal static partial Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer); }

然后調(diào)用?Kernel32.dll?中的函數(shù):

public class Program{ ? ?static void Main() ? ?{ ? ? ? ?var result = GetValue(); ? ? ? ?Console.WriteLine($"當(dāng)前實(shí)際可用內(nèi)存量:{result.ullAvailPhys / 1000 / 1000}MB"); ? ? ? ?Console.ReadKey(); ? ?} ? ? ? ?/// <exception cref="Win32Exception"></exception> ? ?public static MemoryStatusExE GetValue() ? ?{ ? ? ? ?var memoryStatusEx = new MemoryStatusExE(); ? ? ? ?// 重新初始化結(jié)構(gòu)的大小 ? ? ? ?memoryStatusEx.Refresh(); ? ? ? ?// 刷新值 ? ? ? ?if (!Native.GlobalMemoryStatusEx(ref memoryStatusEx)) throw new Win32Exception("無(wú)法獲得內(nèi)存信息"); ? ? ? ?return memoryStatusEx; ? ?} }

使用 AOT 發(fā)布項(xiàng)目,執(zhí)行?CsharpAot.exe?文件。

減少體積

在前面兩個(gè)例子中可以看到?CsharpAot.exe?文件大約在 3MB 左右,但是這個(gè)文件還是太大了,那么我們?nèi)绾芜M(jìn)一步減少 AOT 文件的大小呢?

讀者可以從這里了解如何裁剪程序:https://learn.microsoft.com/zh-cn/dotnet/core/deploying/trimming/trim-self-contained

需要注意的是,裁剪是沒有那么簡(jiǎn)單的,里面配置繁多,有一些選項(xiàng)不能同時(shí)使用,每個(gè)選項(xiàng)又能帶來(lái)什么樣的效果,這些選項(xiàng)可能會(huì)讓開發(fā)者用得很迷茫。

經(jīng)過(guò)筆者的大量測(cè)試,筆者選用了以下一些配置,能夠達(dá)到很好的裁剪效果,供讀者測(cè)試。

首先,引入一個(gè)庫(kù):

<ItemGroup> <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" /> </ItemGroup>

接著,在項(xiàng)目文件中加入以下選項(xiàng):

<!--AOT 相關(guān)--> <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>

最后,發(fā)布項(xiàng)目。

吃驚!生成的可執(zhí)行文件只有 1MB 了,而且還可以正常執(zhí)行。

筆者注:雖然現(xiàn)在看起來(lái) AOT 的文件很小了,但是如果使用到?HttpClient、System.Text.Json?等庫(kù),哪怕只用到了一兩個(gè)函數(shù),最終包含這些庫(kù)以及這些庫(kù)使用到的依賴,生成的 AOT 文件會(huì)大得驚人。

所以,如果項(xiàng)目中使用到其他 nuget 包的時(shí)候,別想著生成的 AOT 能小多少!

C# 導(dǎo)出函數(shù)

這一步可以從時(shí)總的博客中學(xué)習(xí)更多:https://www.cnblogs.com/InCerry/p/CSharp-Dll-Export.html

PS:時(shí)總真的太強(qiáng)了。

在 C 語(yǔ)言中,導(dǎo)出一個(gè)函數(shù)的格式可以這樣:

// MyCFuncs.h#ifdef __cplusplusextern "C" { ?// only need to export C interface if ? ? ? ? ? ? ?// used by C++ source code#endif__declspec( dllimport ) void MyCFunc(); __declspec( dllimport ) void AnotherCFunc();#ifdef __cplusplus}#endif

當(dāng)代碼編譯之后,我們就可以通過(guò)引用生成的庫(kù)文件,調(diào)用?MyCFunc、AnotherCFunc?兩個(gè)方法。

如果不導(dǎo)出的話,別的程序是無(wú)法調(diào)用庫(kù)文件里面的函數(shù)。

因?yàn)?.NET 7 的 AOT 做了很多改進(jìn),因此,.NET 程序也可以導(dǎo)出函數(shù)了。

新建一個(gè)項(xiàng)目,名字就叫?CsharpExport?吧,我們接下來(lái)就在這里項(xiàng)目中編寫我們的動(dòng)態(tài)鏈接庫(kù)。

添加一個(gè)?CsharpExport.cs?文件,內(nèi)容如下:

using System.Runtime.InteropServices;namespace CsharpExport{ ? ?public class Export ? ?{ ? ? ? ?[UnmanagedCallersOnly(EntryPoint = "Add")] ? ? ? ?public static int Add(int a, int b) ? ? ? ?{ ? ? ? ? ? ?return a + b; ? ? ? ?} ? ?} }

然后在?.csproj?文件中,加上?PublishAot?選項(xiàng)。

然后通過(guò)以下命令發(fā)布項(xiàng)目,生成鏈接庫(kù):

dotnet publish -p:NativeLib=Shared -r win-x64 -c Release

BASH復(fù)制全屏

看起來(lái)還是比較大,為了繼續(xù)裁剪體積,我們可以在?CsharpExport.csproj?中加入以下配置,以便生成更小的可執(zhí)行文件。

<!--AOT 相關(guān)--> <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# 調(diào)用 C# 生成的 AOT

在本小節(jié)中,將使用?CsharpAot?項(xiàng)目調(diào)用?CsharpExport?生成的動(dòng)態(tài)鏈接庫(kù)。

把?CsharpExport.dll?復(fù)制到?CsharpAot?項(xiàng)目中,并配置?始終復(fù)制。

在?CsharpAot?的?Native?中加上:

? ?[LibraryImport("CsharpExport.dll", SetLastError = true)] ? ?[return: MarshalAs(UnmanagedType.I4)] ? ?internal static partial Int32 Add(Int32 a, Int32 b);

然后在代碼中使用:

? ?static void Main() ? ?{ ? ? ? ?var result = Native.Add(1, 2); ? ? ? ?Console.WriteLine($"1 + 2 = {result}"); ? ? ? ?Console.ReadKey(); ? ?}

在 Visual Studio 里啟動(dòng) Debug 調(diào)試:

可以看到,是正常運(yùn)行的。

接著,將?CsharpAot?項(xiàng)目發(fā)布為 AOT 后,再次執(zhí)行:

可以看到,.NET AOT 調(diào)用 .NET AOT 的代碼是沒有問題的。


.NET 7 AOT 的使用方法的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
锦州市| 东平县| 柘城县| 资阳市| 西林县| 阿克苏市| 荥阳市| 桃园市| 博客| 杂多县| 渭南市| 黎城县| 兰州市| 昌图县| 铁岭县| 通河县| 库尔勒市| 阿克陶县| 垣曲县| 页游| 澜沧| 勐海县| 固原市| 普洱| 东兴市| 云林县| 灵武市| 福海县| 万盛区| 汉寿县| 邵武市| 广南县| 民乐县| 永德县| 漳浦县| 历史| 德令哈市| 安丘市| 乡城县| 新巴尔虎右旗| 获嘉县|