Loading

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
posted @ 2024-09-07 17:04  J.晒太阳的猫  阅读(134)  评论(0编辑  收藏  举报