自定义 P/Invoke
LeanCLR 支持自定义 P/Invoke,在托管 C# 与原生 C++ 之间互操作。为保持跨平台可移植性,P/Invoke 目标目前须为 AOT 编译的原生函数;在 IL→AOT 工具链完全自动化之前,需手动注册原生实现。
各平台(Win64、WebAssembly、Unity 发布目标等)使用同一套注册 API,无平台分支写法。参考示例:
src/samples/custom-pinvoke-x64
概述
流程两步:
- 在 C# 中声明
externP/Invoke 方法 - 在 C++ 中实现原生函数,并在运行时
register_pinvoke_func注册 invoker
C# 侧定义
using System.Runtime.InteropServices;
namespace test
{
public class CustomPInvoke
{
[DllImport("CustomNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);
}
}
备注
由于绑定靠手动注册,DllImport 的 DLL 名与 CallingConvention 目前为占位符,不影响实际绑定。
C++ 侧注册
包含头文件
#include "public/leanclr.hpp"
// C 项目可用 #include "public/leanclr.h"
实现原生函数
int32_t my_add(int32_t a, int32_t b)
{
return a + b;
}
编写 Invoker
LeanCLR 通过固定签名的 invoker 从托管栈取参并写回返回值:
RtResultVoid my_add_invoker(metadata::RtManagedMethodPointer,
const metadata::RtMethodInfo*,
const interp::RtStackObject* params,
interp::RtStackObject* ret)
{
size_t offset = 0;
auto a = RuntimeApi::get_argument<int32_t>(params, offset);
auto b = RuntimeApi::get_argument<int32_t>(params, offset);
int32_t result = my_add(a, b);
RuntimeApi::set_return_value<int32_t>(ret, result);
RET_VOID_OK();
}
Invoker 签名必须为:
RtResultVoid (metadata::RtManagedMethodPointer,
const metadata::RtMethodInfo*,
const interp::RtStackObject* params,
interp::RtStackObject* ret)
get_argument<T>(params, offset)—offset为栈槽偏移,结构体参数可能占多槽set_return_value<T>(ret, value)— 写回返回值RET_VOID_OK()— 返回成功
注册
在 vm::Runtime::initialize() 成功之后:
void RegisterCustomPInvokeMethods()
{
RuntimeApi::register_pinvoke_func(
"[CoreTests]test.CustomPInvoke::Add(System.Int32,System.Int32)",
(vm::PInvokeFunction)&my_add,
my_add_invoker);
}
int main()
{
auto ret = vm::Runtime::initialize();
if (ret.is_err()) return -1;
RegisterCustomPInvokeMethods();
// ...
}
签名字符串格式:
[<dll>]<fullname>::<method>(T1,T2,...,TN)
无冲突时可省略 [dll] 前缀,使用短形式 <fullname>::<method>(...)。
参数:
| 参数 | 说明 |
|---|---|
| signature | 与 C# 声明一致,含命名空间、类型、方法名、参数类型 |
| pinvoke_func | 原生实现指针 |
| pinvoke_invoker | 上述 invoker |
最佳实践
- 在
initialize之后注册 - 签名字符串与 C# 元数据严格一致
- 使用
RuntimeApi辅助函数访问栈,避免手工算偏移 - 换平台时复用同一套 C# 声明与
register_pinvoke_func逻辑,仅原生函数实现与链接方式随宿主工具链变化
LeanAOT 自动生成(展望)
LeanAOT toolchain 发展后,部分 P/Invoke 包装可由 AOT 自动生成。当前以手动注册为准。