聊一聊 C# NativeAOT 多平台下的函数导出

笔记哥 / 05-28 / 21点赞 / 0评论 / 402阅读
## 一:背景 ### 1. 讲故事 昨晚训练营里有一位朋友提到一个问题,说 C# AOT程序能否编译为一个dll,供其他语言调用,其实这个是完全没有问题的,也确实我的的文章体系中没有涉及到这块,那今天就补充完整吧。 ## 二:NativeAOT 函数导出 ### 1. 简单的案例 在 C 中我相信很多人都知道用 `dllexport` 进行函数导出,如下所示: ```C extern "C" {_declspec(dllexport) void ComplexCalculation(); } ``` 在 C# 中其实也差不多,用 `UnmanagedCallersOnly` 特性替代即可,接下来创建一个 C# 的类库,参考代码如下: ```C internal class ExportMethods { [UnmanagedCallersOnly(EntryPoint = "ComplexCalculation")] public static int ComplexCalculation(int a, int b, IntPtr stringInput) { try { // 1. 处理字符串参数(从非托管代码传入的 char*) string? inputStr = Marshal.PtrToStringAnsi(stringInput); Console.WriteLine($"Input string: {a},{b}, {inputStr}"); // 2. 复杂计算逻辑 int result = a + b; if (inputStr?.ToLower() == "double") { result *= 2; } return result; } catch (Exception ex) { Console.WriteLine(ex.Message); return -1; } } } ``` 在 csproject 中追加最后二行,其中的 `Shared` 表示可以生成动态链接库,即 windows 上的 .dll 以及 linux 上的 .so 文件。 ```xml net8.0 disable enable AnyCPU;x86 true Shared ``` 配置好之后就可以用 `dotnet publish` 发布为 windows 的原生动态链接库,参考如下: ```powershell PS D:\skyfly\20.20250116\src\Example\Example_20_1_1> dotnet publish -r win-x64 -o D:\testdump 还原完成(0.3) Example_20_1_1 已成功 (1.5) → D:\testdump\ 在 2.1 中生成 已成功 PS D:\skyfly\20.20250116\src\Example\Example_20_1_1> ``` 生成好 dll 之后可以用 PPEE 工具观察下 export 表,打开之后果然有了 `ComplexCalculation` 函数。 ![](https://cdn.res.knowhub.vip/c/2505/28/c0bf0d85.png?G1QAAMR0rnGCfldr3EYcqgkCCTYDElUElXrW6%2fn%2ftS%2bi90tlRb1H6zP3hz%2b0PpNUEBBSVmdH8XqKMCwOi2IGh3igrpE%3d) 接下来大家可以把这个 dll 提供给 C 或者 C# 去调用,只要 PE头里有,怎么开心怎么玩。 这里新建一个 `Example_20_1_6` 的 C# 控制台程序,使用传统的 DllImport 导入外部方法,就像以前引入C的外部方法一样,参考代码如下: ```C internal class Program { [DllImport("Example_20_1_1", CallingConvention = CallingConvention.StdCall)] extern static int ComplexCalculation(int a, int b, IntPtr stringInput); static void Main(string[] args) { Console.WriteLine("准备调用原生方法..."); // 1. 将托管字符串转换为非托管指针 IntPtr stringPtr = Marshal.StringToHGlobalAnsi("double"); try { // 2. 调用原生方法 int result = ComplexCalculation(10, 20, stringPtr); Console.WriteLine($"调用完成,结果: {result}"); } finally { // 3. 释放非托管内存 Marshal.FreeHGlobal(stringPtr); } } } ``` 将 `Example_20_1_1.dll` 拷贝到当前的bin目录下,运行程序之后,一切ok,截图如下: ![](https://cdn.res.knowhub.vip/c/2505/28/16cf1a0b.png?G1UAAER17rxgax2Cfice0wSBBJqBijSCSj3r9e491y3y%2fU6lxafXNnx9%2bEVtw4WwbBAqkyaEwAKoHfnMFkBasqsAcXYH) ### 2. linux 上的投放 刚才只是在 windows 平台上的演示,接下来试部署到 `ubuntu` 上,正常情况下你可能不会那么一帆风顺,不是缺少这个库就是那个库,比如下面的报错。 ```shell root@ubuntu2404:/data2/Example_20_1_1# dotnet publish -r linux-x64 -o ./dll MSBuild version 17.8.22+bfbb05667 for .NET Determining projects to restore... Restored /data2/Example_20_1_1/Example_20_1_1.csproj (in 27.42 sec). Example_20_1_1 -> /data2/Example_20_1_1/bin/Release/net8.0/linux-x64/Example_20_1_1.dll Generating native code /usr/bin/ld.bfd: cannot find -lz: No such file or directory ``` 上面就是典型的缺少 `zlib` 包,安装一下即可,所以大家也是根据报错一个一个解决,最终肯定会走出迷雾。 ```shell root@ubuntu2404:/data2/Example_20_1_1# sudo apt update && sudo apt install zlib1g-dev root@ubuntu2404:/data2/Example_20_1_1# dotnet publish -r linux-x64 -o ../ MSBuild version 17.8.22+bfbb05667 for .NET Determining projects to restore... All projects are up-to-date for restore. Example_20_1_1 -> /data2/Example_20_1_1/bin/Release/net8.0/linux-x64/Example_20_1_1.dll Example_20_1_1 -> /data2/ root@ubuntu2404:/data2/Example_20_1_1# ls -lh ../ total 4.0M drwxr-xr-x 5 root root 4.0K May 28 10:20 Example_20_1_1 -rw-r--r-- 1 root root 1.5M May 28 10:26 Example_20_1_1.so -rwxr-xr-x 1 root root 2.5M May 28 10:26 Example_20_1_1.so.dbg -rw-r--r-- 1 root root 0 May 28 10:18 app.c ``` 有了这个 so 文件后,接下来我们新建一个 c文件,参考代码如下: ```C #include #include // 动态加载库 int main() { void *handle = dlopen("./Example_20_1_1.so", RTLD_LAZY); if (!handle) { fprintf(stderr, "Error: %s\n", dlerror()); return 1; } // 获取函数指针 int (*ComplexCalculation)(int, int, const char *) = (int (*)(int, int, const char *))dlsym(handle, "ComplexCalculation"); if (!ComplexCalculation) { fprintf(stderr, "Error: %s\n", dlerror()); dlclose(handle); return 1; } // 调用函数 int result = ComplexCalculation(10, 20, "double"); printf("Result: %d\n", result); dlclose(handle); // 关闭句柄 return 0; } ``` 使用 vscode 远程调试,哈哈,得到了我们想要的结果,截图如下: ![](https://cdn.res.knowhub.vip/c/2505/28/c3cd4b86.png?G1QAAETn9LwUqCDLvtMdbIlTE20GJKoIKvWs13vO2jfR9wcYmp%2fR%2boz94Q%2btzyCIugqBYWxIHpcIa%2fFaalK36jCXvEYA) ## 三:总结 这篇我们演示了 windows 上的 `C# 调用 C# AOT` 及 linux 上的 `C 调用 C# AOT`,是不是挺有意思,也算是给训练营学员提供的一份资料参考。