重庆涪陵建设银行网站,微信公众号怎么做成微网站,怎么建一个购物网站,安云自助建站系统源码背景其实#xff0c;规划这篇文章有一段时间了#xff0c;但是比较懒#xff0c;所以一直拖着没写。最近时总更新太快了#xff0c;太卷了#xff0c;所以借着 .NET 7 正式版发布#xff0c;熬夜写完这篇文章#xff0c;希望能够追上时总的一点距离。本文主要介绍如何在…背景其实规划这篇文章有一段时间了但是比较懒所以一直拖着没写。最近时总更新太快了太卷了所以借着 .NET 7 正式版发布熬夜写完这篇文章希望能够追上时总的一点距离。本文主要介绍如何在 .NET 和 Go 语言中如何生成系统(Windows)动态链接库又如何从代码中引用这些库中的函数。在 .NET 部分介绍如何使用 AOT、减少二进制文件大小、使用最新的 [LibraryImport] 导入库函数在 Go 语言部分介绍如何使用 GCC 编译 Go 代码、如何通过 syscall 导入库函数。在文章中会演示 .NET 和 Go 相互调用各自生成的动态链接库以及对比两者之间的差异。C# 部分环境要求SDK.NET 7 SDK、Desktop development with C workload。IDEVisual Studio 2022Desktop development with C workload 是一个工具集里面包含 C 开发工具需要在 Visual Studio Installer 中安装如下图红框中所示。创建一个控制台项目首先创建一个 .NET 7 控制台项目名称为 CsharpAot。打开项目之后基本代码如图所示我们使用下面的代码做测试publicclassProgram
{staticvoidMain(){Console.WriteLine(C# Aot!);Console.ReadKey();}
}体验 AOT 编译这一步可以参考官方网站的更多说明为了能够让项目发布时使用 AOT 模式需要在项目文件中加上 PublishAottrue/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 而不是使用构造函数/remarksinternal UInt32 dwLength;///summary/// 一个介于 0 和 100 之间的数字用于指定正在使用的物理内存的大致百分比0 表示没有内存使用100 表示内存已满。////summaryinternal UInt32 dwMemoryLoad;///summary/// 实际物理内存量以字节为单位////summaryinternal UInt64 ullTotalPhys;///summary/// 当前可用的物理内存量以字节为单位。这是可以立即重用而无需先将其内容写入磁盘的物理内存量。它是备用列表、空闲列表和零列表的大小之和////summaryinternal UInt64 ullAvailPhys;///summary/// 系统或当前进程的当前已提交内存限制以字节为单位以较小者为准。要获得系统范围的承诺内存限制请调用GetPerformanceInfo////summaryinternal UInt64 ullTotalPageFile;///summary/// 当前进程可以提交的最大内存量以字节为单位。该值等于或小于系统范围的可用提交值。要计算整个系统的可承诺值调用GetPerformanceInfo核减价值CommitTotal从价值CommitLimit////summaryinternal UInt64 ullAvailPageFile;///summary/// 调用进程的虚拟地址空间的用户模式部分的大小以字节为单位。该值取决于进程类型、处理器类型和操作系统的配置。例如对于 x86 处理器上的大多数 32 位进程此值约为 2 GB对于在启用4 GB 调整的系统上运行的具有大地址感知能力的 32 位进程约为 3 GB 。////summaryinternal UInt64 ullTotalVirtual;///summary/// 当前在调用进程的虚拟地址空间的用户模式部分中未保留和未提交的内存量以字节为单位////summaryinternal UInt64 ullAvailVirtual;///summary/// 预订的。该值始终为 0////summaryinternal UInt64 ullAvailExtendedVirtual;internalvoidRefresh(){dwLength checked((UInt32)Marshal.SizeOf(typeof(MemoryStatusExE)));}
}定义引用库函数的入口publicstaticpartialclassNative
{///summary/// 检索有关系统当前使用物理和虚拟内存的信息////summary///param namelpBuffer/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 crefWin32Exception/exceptionpublicstatic MemoryStatusExE GetValue(){var memoryStatusEx new MemoryStatusExE();// 重新初始化结构的大小memoryStatusEx.Refresh();// 刷新值if (!Native.GlobalMemoryStatusEx(ref memoryStatusEx)) thrownew Win32Exception(无法获得内存信息);return memoryStatusEx;}
}使用 AOT 发布项目执行 CsharpAot.exe 文件。减少体积在前面两个例子中可以看到 CsharpAot.exe 文件大约在 3MB 左右但是这个文件还是太大了那么我们如何进一步减少 AOT 文件的大小呢需要注意的是裁剪是没有那么简单的里面配置繁多有一些选项不能同时使用每个选项又能带来什么样的效果这些选项可能会让开发者用得很迷茫。经过笔者的大量测试笔者选用了以下一些配置能够达到很好的裁剪效果供读者测试。首先引入一个库 ItemGroupPackageReference IncludeMicrosoft.DotNet.ILCompiler Version7.0.0-* //ItemGroup接着在项目文件中加入以下选项 !--AOT 相关--PublishAottrue/PublishAot TrimModefull/TrimModeRunAOTCompilationTrue/RunAOTCompilationPublishTrimmedtrue/PublishTrimmedTrimmerRemoveSymbolstrue/TrimmerRemoveSymbolsPublishReadyToRunEmitSymbolsfalse/PublishReadyToRunEmitSymbolsDebuggerSupportfalse/DebuggerSupportEnableUnsafeUTF7Encodingtrue/EnableUnsafeUTF7EncodingInvariantGlobalizationtrue/InvariantGlobalizationHttpActivityPropagationSupportfalse/HttpActivityPropagationSupportMetadataUpdaterSupporttrue/MetadataUpdaterSupportUseSystemResourceKeystrue/UseSystemResourceKeysIlcDisableReflection true/IlcDisableReflection最后发布项目。吃惊生成的可执行文件只有 1MB 了而且还可以正常执行。笔者注虽然现在看起来 AOT 的文件很小了但是如果使用到 HttpClient、System.Text.Json 等库哪怕只用到了一两个函数最终包含这些库以及这些库使用到的依赖生成的 AOT 文件会大得惊人。所以如果项目中使用到其他 nuget 包的时候别想着生成的 AOT 能小多少C# 导出函数在 C 语言中导出一个函数的格式可以这样// MyCFuncs.h#ifdef __cplusplusexternC { // 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:NativeLibShared -r win-x64 -c Release看起来还是比较大为了继续裁剪体积我们可以在 CsharpExport.csproj 中加入以下配置以便生成更小的可执行文件。 !--AOT 相关--PublishAottrue/PublishAotTrimModefull/TrimModeRunAOTCompilationTrue/RunAOTCompilationPublishTrimmedtrue/PublishTrimmedTrimmerRemoveSymbolstrue/TrimmerRemoveSymbolsPublishReadyToRunEmitSymbolsfalse/PublishReadyToRunEmitSymbolsDebuggerSupportfalse/DebuggerSupportEnableUnsafeUTF7Encodingtrue/EnableUnsafeUTF7EncodingInvariantGlobalizationtrue/InvariantGlobalizationHttpActivityPropagationSupportfalse/HttpActivityPropagationSupportMetadataUpdaterSupporttrue/MetadataUpdaterSupportUseSystemResourceKeystrue/UseSystemResourceKeysIlcDisableReflection true/IlcDisableReflectionC# 调用 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 的代码是没有问题的。