rust导出函数必须用extern "c"并加#[no_mangle],c# p/invoke需匹配callingconvention.cdecl、类型及内存管理规则,否则报entrypointnotfoundexception或崩溃。

确认Rust导出函数必须用 extern "C" 且禁用mangling
Rust默认不生成C兼容符号,直接 pub fn 无法被C# P/Invoke识别。必须显式声明为C ABI,并用 #[no_mangle] 防止符号重命名。否则C#调用时会报 System.EntryPointNotFoundException。
- 在Rust中导出函数前加
extern "C" { ... }块或直接写extern "C" fn - 必须加
#[no_mangle],否则链接器看到的是类似_ZN3foo3bar17habc123...的符号 - 避免返回Rust专有类型(如
String、Vec、&str),只用C基本类型或手动管理的裸指针 - 如果需要返回字符串,推荐用
*const i8(即const char*)并由C#负责Marshal.PtrToStringAnsi转换
C#端P/Invoke声明要严格匹配Rust函数签名
参数顺序、类型、调用约定(CallingConvention.Cdecl)三者必须与Rust侧完全一致,否则可能崩溃或读取垃圾内存。
- 务必在
[DllImport]中指定CallingConvention = CallingConvention.Cdecl— Rust FFI默认用Cdecl,Windows上若省略会默认StdCall,导致栈不平衡 - Rust的
i32对应C#的int,u64对应ulong,f64对应double - 传入字符串时,Rust侧接收
*const i8,C#用string+[MarshalAs(UnmanagedType.LPStr)];传出字符串则Rust返回*const i8,C#用IntPtr接收后手动转换 - 结构体需用
[StructLayout(LayoutKind.Sequential)],字段对齐按Rust中#[repr(C)]定义
处理内存生命周期:谁分配谁释放
Rust和C#各自管理内存,跨FFI传递堆内存必须明确所有权边界,否则必然出现use-after-free或内存泄漏。
- 如果Rust函数返回堆分配的字符串(如
Box::into_raw(...)包装的*const i8),必须配套提供一个free_string(ptr: *mut u8)函数供C#调用释放 - C#不应尝试
Marshal.FreeHGlobal释放Rust分配的内存 — 分配器不同(Rust用system allocator或jemalloc,.NET用自己的heap) - 简单场景下,优先让Rust函数把结果写入C#预分配的缓冲区(
*mut i8+len: usize),避免跨语言内存管理 - 回调函数需用
Box::new(FnOnce)+Box::into_raw传指针,再由Rust在适当时机调用并Box::from_raw恢复销毁
构建与加载DLL:平台与ABI对齐是硬门槛
生成的Rust动态库(.dll / .so / .dylib)必须与C#运行时目标平台完全一致,否则 DllNotFoundException 或直接拒绝加载。
- Rust crate需设
crate-type = ["cdylib"](不是rlib或lib) - 编译目标必须匹配:C#项目是
x64,Rust就得用cargo build --release --target x86_64-pc-windows-msvc(MSVC)或x86_64-pc-windows-gnu(GNU),不能混用 - Windows上若Rust用GNU工具链(MinGW),C#需确保运行时能找到
libgcc和libstdc++;MSVC更稳妥 - 将生成的
target\release\your_lib.dll复制到C#输出目录(如bin\Debug\net8.0\),或用[DllImport("your_lib.dll", EntryPoint = "...")]显式指定路径
C#调用Rust最常卡在符号找不到或一调就崩,核心就两条:Rust端是否真正导出了C ABI符号,C#端是否用对了调用约定和内存契约。这两处错一点,后面所有逻辑都白搭。










