C++ 调用 C# - AOT 方案
一些 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 就用不了
C# 端的 demo 代码
[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
以及相关的依赖
C++ 端的 demo 代码
// 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++ 端的调用目录。
以下是一个辅助脚本,供参考。
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
作者:
J.晒太阳的猫
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。