找回密码
 立即注册
首页 业界区 业界 聊一聊 C# NativeAOT 多平台下的函数导出

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

于映雪 2025-6-3 00:42:20
一:背景

1. 讲故事

昨晚训练营里有一位朋友提到一个问题,说 C# AOT程序能否编译为一个dll,供其他语言调用,其实这个是完全没有问题的,也确实我的的文章体系中没有涉及到这块,那今天就补充完整吧。
二:NativeAOT 函数导出

1. 简单的案例

在 C 中我相信很多人都知道用 dllexport 进行函数导出,如下所示:
  1. extern "C"
  2. {
  3.         _declspec(dllexport) void ComplexCalculation();
  4. }
复制代码
在 C# 中其实也差不多,用 UnmanagedCallersOnly 特性替代即可,接下来创建一个 C# 的类库,参考代码如下:
  1. internal class ExportMethods
  2. {
  3.     [UnmanagedCallersOnly(EntryPoint = "ComplexCalculation")]
  4.     public static int ComplexCalculation(int a, int b, IntPtr stringInput)
  5.     {
  6.         try
  7.         {
  8.             // 1. 处理字符串参数(从非托管代码传入的 char*)
  9.             string? inputStr = Marshal.PtrToStringAnsi(stringInput);
  10.             Console.WriteLine($"Input string: {a},{b}, {inputStr}");
  11.             // 2. 复杂计算逻辑
  12.             int result = a + b;
  13.             if (inputStr?.ToLower() == "double")
  14.             {
  15.                 result *= 2;
  16.             }
  17.             return result;
  18.         }
  19.         catch (Exception ex)
  20.         {
  21.             Console.WriteLine(ex.Message);
  22.             return -1;
  23.         }
  24.     }
  25. }
复制代码
在 csproject 中追加最后二行,其中的 Shared 表示可以生成动态链接库,即 windows 上的 .dll 以及 linux 上的 .so 文件。
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.         <PropertyGroup>
  3.                 <TargetFramework>net8.0</TargetFramework>
  4.                 <ImplicitUsings>disable</ImplicitUsings>
  5.                 <Nullable>enable</Nullable>
  6.                 <Platforms>AnyCPU;x86</Platforms>
  7.           
  8.                 <PublishAot>true</PublishAot>
  9.                 <NativeLib>Shared</NativeLib>
  10.         </PropertyGroup>
  11. </Project>
复制代码
配置好之后就可以用 dotnet publish 发布为 windows 的原生动态链接库,参考如下:
  1. PS D:\skyfly\20.20250116\src\Example\Example_20_1_1> dotnet publish -r win-x64  -o D:\testdump
  2. 还原完成(0.3)
  3.   Example_20_1_1 已成功 (1.5) → D:\testdump\
  4. 在 2.1 中生成 已成功
  5. PS D:\skyfly\20.20250116\src\Example\Example_20_1_1>
复制代码
生成好 dll 之后可以用 PPEE 工具观察下 export 表,打开之后果然有了 ComplexCalculation 函数。
1.png

接下来大家可以把这个 dll 提供给 C 或者 C# 去调用,只要 PE头里有,怎么开心怎么玩。
这里新建一个 Example_20_1_6 的 C# 控制台程序,使用传统的 DllImport 导入外部方法,就像以前引入C的外部方法一样,参考代码如下:
  1. internal class Program
  2. {
  3.     [DllImport("Example_20_1_1", CallingConvention = CallingConvention.StdCall)]
  4.     extern static int ComplexCalculation(int a, int b, IntPtr stringInput);
  5.     static void Main(string[] args)
  6.     {
  7.         Console.WriteLine("准备调用原生方法...");
  8.         // 1. 将托管字符串转换为非托管指针
  9.         IntPtr stringPtr = Marshal.StringToHGlobalAnsi("double");
  10.         try
  11.         {
  12.             // 2. 调用原生方法
  13.             int result = ComplexCalculation(10, 20, stringPtr);
  14.             Console.WriteLine($"调用完成,结果: {result}");
  15.         }
  16.         finally
  17.         {
  18.             // 3. 释放非托管内存
  19.             Marshal.FreeHGlobal(stringPtr);
  20.         }
  21.     }
  22. }
复制代码
将 Example_20_1_1.dll 拷贝到当前的bin目录下,运行程序之后,一切ok,截图如下:
2.png

2. linux 上的投放

刚才只是在 windows 平台上的演示,接下来试部署到 ubuntu 上,正常情况下你可能不会那么一帆风顺,不是缺少这个库就是那个库,比如下面的报错。
  1. root@ubuntu2404:/data2/Example_20_1_1#  dotnet publish -r linux-x64 -o ./dll
  2. MSBuild version 17.8.22+bfbb05667 for .NET
  3.   Determining projects to restore...
  4.   Restored /data2/Example_20_1_1/Example_20_1_1.csproj (in 27.42 sec).
  5.   Example_20_1_1 -> /data2/Example_20_1_1/bin/Release/net8.0/linux-x64/Example_20_1_1.dll
  6.   Generating native code
  7.   /usr/bin/ld.bfd: cannot find -lz: No such file or directory
复制代码
上面就是典型的缺少 zlib 包,安装一下即可,所以大家也是根据报错一个一个解决,最终肯定会走出迷雾。
  1. root@ubuntu2404:/data2/Example_20_1_1# sudo apt update && sudo apt install zlib1g-dev
  2. root@ubuntu2404:/data2/Example_20_1_1# dotnet publish -r linux-x64 -o ../
  3. MSBuild version 17.8.22+bfbb05667 for .NET
  4.   Determining projects to restore...
  5.   All projects are up-to-date for restore.
  6.   Example_20_1_1 -> /data2/Example_20_1_1/bin/Release/net8.0/linux-x64/Example_20_1_1.dll
  7.   Example_20_1_1 -> /data2/
  8. root@ubuntu2404:/data2/Example_20_1_1# ls -lh ../
  9. total 4.0M
  10. drwxr-xr-x 5 root root 4.0K May 28 10:20 Example_20_1_1
  11. -rw-r--r-- 1 root root 1.5M May 28 10:26 Example_20_1_1.so
  12. -rwxr-xr-x 1 root root 2.5M May 28 10:26 Example_20_1_1.so.dbg
  13. -rw-r--r-- 1 root root    0 May 28 10:18 app.c
复制代码
有了这个 so 文件后,接下来我们新建一个 c文件,参考代码如下:
  1. #include <stdio.h>
  2. #include <dlfcn.h> // 动态加载库
  3. int main()
  4. {
  5.     void *handle = dlopen("./Example_20_1_1.so", RTLD_LAZY);
  6.    
  7.     if (!handle)
  8.     {
  9.         fprintf(stderr, "Error: %s\n", dlerror());
  10.         return 1;
  11.     }
  12.     // 获取函数指针
  13.     int (*ComplexCalculation)(int, int, const char *) =
  14.         (int (*)(int, int, const char *))dlsym(handle, "ComplexCalculation");
  15.     if (!ComplexCalculation)
  16.     {
  17.         fprintf(stderr, "Error: %s\n", dlerror());
  18.         dlclose(handle);
  19.         return 1;
  20.     }
  21.     // 调用函数
  22.     int result = ComplexCalculation(10, 20, "double");
  23.     printf("Result: %d\n", result);
  24.     dlclose(handle); // 关闭句柄
  25.     return 0;
  26. }
复制代码
使用 vscode 远程调试,哈哈,得到了我们想要的结果,截图如下:
3.png

三:总结

这篇我们演示了 windows 上的 C# 调用 C# AOT 及 linux 上的 C 调用 C# AOT,是不是挺有意思,也算是给训练营学员提供的一份资料参考。
4.jpg


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册