一些 C# AOT 编译的笔记,整体感觉:简单很方便,但限制也很多,适用于比较单一的功能点。
跨语言调用C#代码的新方式-DllExport - InCerry - 博客园
在 .NET8 下,直接添加 <PublishAot>true</PublishAot>
就可以支持了, 需要注意一些限制,这里比较相关的是,不能使用 Newtonsoft.Json 做序列化,可以使用原生的 System.Text.Json 代替
更多说明和限制,可以看:
Native AOT deployment overview - .NET | Microsoft Learn
Create a single file for application deployment - .NET | Microsoft Learn
因为反射等特性受限,无法动态加载程序集,很多功能会用不了,如果是长期维护的项目,后续很有可能会遇到相关的坑。
比如 linq2db 就用不了
[UnmanagedCallersOnly(EntryPoint = "Combine")]public static IntPtr Combine(IntPtr str, int num){ var name = Class1.Run(); string? myStr = Marshal.PtrToStringAnsi(str); string result = $"{myStr} -- {num} -- {name}"; return Marshal.StringToHGlobalAnsi(result);}
[UnmanagedCallersOnly(EntryPoint = "Free")]public static void Free(IntPtr ptr){ Marshal.FreeHGlobal(ptr);}
csproj 设置
<PropertyGroup> <PublishAot>true</PublishAot> <IsAotCompatible>true</IsAotCompatible> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies></PropertyGroup>
CopyLocalLockFileAssemblies
是将依赖都输出到输出目录。
C# 编译结果:Accesser.dll
以及相关的依赖
VisitByAot.h
#pragma once
#define PathToLibrary L"SubFolder\\Accesser.dll"#define PathToLibraryFolder L"SubFolder"
#include <windows.h>#include <iostream>#include "PathHelper.h"
class VisitByAot{public: int run();
private: // 定义函数指针类型 typedef const char* (__stdcall* CombineFunc)(const char*, int); typedef void(__stdcall* FreeFunc)(const char*);};
PathHelper.h
中的是一些辅助函数,见函数名知意。
PathToLibrary 和 PathToLibraryFolder 的配置,是为了将 C# 的 dll 放到 C++ 输出目录下的子文件夹中,让 DLL 更清晰一点。
VisitByAot.cpp
#include "VisitByAot.h"
int VisitByAot::run(){ std::cout << "Hello World!\n";
// 获取当前工作目录 std::wstring currentDirectory = PathHelper::GetExecutableDirectory();
// 生成绝对路径 std::wstring pathToLibrary = PathHelper::CombinePath(currentDirectory, PathToLibrary); std::wstring pathToDllFolder = PathHelper::CombinePath(currentDirectory, PathToLibraryFolder);
// 设置 DLL 搜索路径 SetDllDirectory(pathToDllFolder.c_str());
// 加载 DLL HINSTANCE hinstLib = LoadLibrary(pathToLibrary.c_str()); if (hinstLib == NULL) { std::cerr << "Could not load the DLL." << std::endl; return EXIT_FAILURE; }
// 获取 Combine 函数地址 CombineFunc Combine = (CombineFunc)GetProcAddress(hinstLib, "Combine"); if (Combine == NULL) { std::cerr << "Could not locate the function." << std::endl; FreeLibrary(hinstLib); return EXIT_FAILURE; }
// 获取 Free 函数地址 FreeFunc Free = (FreeFunc)GetProcAddress(hinstLib, "Free"); if (Free == NULL) { std::cerr << "Could not locate the Free function." << std::endl; FreeLibrary(hinstLib); return EXIT_FAILURE; }
// 调用函数 const char* result = Combine("example", 123); if (result != NULL) { std::cout << "Result: " << result << std::endl;
//// 使用 GlobalFree 释放内存 //GlobalFree((HGLOBAL)result);
// 使用 C# 导出的 Free 方法释放 Free(result); } else { std::cerr << "Function returned NULL." << std::endl; }
// 释放 DLL FreeLibrary(hinstLib);
return EXIT_SUCCESS;
}
需要注意的是,需要 C++ 调用端释放不再使用的引用。
可以看到,C# 端和 C++ 端是完全隔离的,C++ 端使用 LoadLibrary 的方式加载。所以就需要手动将 C# 的输出,拷贝到 C++ 端的调用目录。
以下是一个辅助脚本,供参考。
Terminal window
cd /d "%~dp0"
set currentDir=%cd%echo current work dir: %currentDir%
set BuildRID=Debug
set AotPublishOutputPath=".\bin\%BuildRID%\net8.0\win-x64\publish"set CppUseTargetPath="..\x64\%BuildRID%\SubFolder"
rd /s /q %AotPublishOutputPath%
:: 生成 AOT 编译结果dotnet publish -p:NativeLib=Shared -r win-x64 -c %BuildRID%
if not exist "%CppUseTargetPath%" ( mkdir "%CppUseTargetPath%")xcopy "%AotPublishOutputPath%\*" "%CppUseTargetPath%\" /E /I /Y
原文链接: https://cloud.tencent.com/developer/article/2481586
本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。