Rust跨语言绑定生成工具csbindgen的使用,实现Rust与C#无缝互操作的高效代码生成
Rust跨语言绑定生成工具csbindgen的使用,实现Rust与C#无缝互操作的高效代码生成
简介
csbindgen是一个用于自动生成C# FFI代码的工具,可以从Rust的extern "C" fn
代码生成C#的DllImport
代码。它针对"Cdecl"调用进行了优化,并且可以配置为输出适用于.NET或Unity的代码。
快速开始
安装
在Cargo.toml
中添加为build-dependencies
:
[package]
name = "example"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[build-dependencies]
csbindgen = "1.8.0"
Rust到C#的绑定生成
Rust代码示例
// lib.rs, 简单的FFI代码
#[no_mangle]
pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
x + y
}
build.rs配置
fn main() {
csbindgen::Builder::default()
.input_extern_file("lib.rs")
.csharp_dll_name("example")
.generate_csharp_file("../dotnet/NativeMethods.g.cs")
.unwrap();
}
生成的C#代码
// NativeMethods.g.cs
using System;
using System.Runtime.InteropServices;
namespace CsBindgen
{
internal static unsafe partial class NativeMethods
{
const string __DllName = "example";
[DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int my_add(int x, int y);
}
}
C到Rust再到C#的绑定生成
示例:集成LZ4压缩库
// 使用bindgen生成绑定代码
bindgen::Builder::default()
.header("c/lz4/lz4.h")
.generate().unwrap()
.write_to_file("lz4.rs").unwrap();
// 使用cc编译和链接C代码
cc::Build::new().file("lz4.c").compile("lz4");
// 使用csbindgen生成Rust FFI和C# DllImport代码
csbindgen::Builder::default()
.input_bindgen_file("lz4.rs") // 从bindgen生成的代码读取
.rust_file_header("use super::lz4::*;") // 导入bindgen生成的模块
.csharp_entry_point_prefix("csbindgen_") // 调整Rust方法和C# EntryPoint的签名匹配
.csharp_dll_name("liblz4")
.generate_to_file("lz4_ffi.rs", "../dotnet/NativeMethods.lz4.g.cs")
.unwrap();
生成的Rust FFI代码
// lz4_ffi.rs
#[allow(unused)]
use ::std::os::raw::*;
use super::lz4::*;
#[no_mangle]
pub unsafe extern "C" fn csbindgen_LZ4_compress_default(src: *const c_char, dst: *mut c_char, srcSize: c_int, dstCapacity: c_int) -> c_int
{
LZ4_compress_default(src, dst, srcSize, dstCapacity)
}
生成的C#代码
// NativeMethods.lz4.g.cs
using System;
using System.Runtime.InteropServices;
namespace CsBindgen
{
internal static unsafe partial class NativeMethods
{
const string __DllName = "liblz4";
[DllImport(__DllName, EntryPoint = "csbindgen_LZ4_compress_default", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int LZ4_compress_default(byte* src, byte* dst, int srcSize, int dstCapacity);
}
}
在lib.rs中导入生成的模块
// lib.rs, 导入生成的代码
#[allow(dead_code)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
mod lz4;
#[allow(dead_code)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
mod lz4_ffi;
完整示例Demo
Rust侧代码
// lib.rs
#[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[no_mangle]
pub extern "C" fn create_point(x: f32, y: f32) -> *mut Point {
Box::into_raw(Box::new(Point { x, y }))
}
#[no_mangle]
pub extern "C" fn get_point_distance(p: *const Point) -> f32 {
unsafe {
let point = &*p;
(point.x * point.x + point.y * point.y).sqrt()
}
}
#[no_mangle]
pub extern "C" fn free_point(p: *mut Point) {
unsafe {
Box::from_raw(p);
}
}
// 回调函数示例
#[no_mangle]
pub extern "C" fn call_with_callback(cb: extern "C" fn(i32) -> i32) -> i32 {
cb(42)
}
build.rs配置
// build.rs
fn main() {
csbindgen::Builder::default()
.input_extern_file("src/lib.rs")
.csharp_dll_name("point_lib")
.csharp_class_name("PointNativeMethods")
.csharp_namespace("Geometry")
.generate_csharp_file("../dotnet/PointNativeMethods.g.cs")
.unwrap();
}
生成的C#代码
// PointNativeMethods.g.cs
using System;
using System.Runtime.InteropServices;
namespace Geometry
{
internal static unsafe partial class PointNativeMethods
{
const string __DllName = "point_lib";
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public float x;
public float y;
}
[DllImport(__DllName, EntryPoint = "create_point", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern Point* create_point(float x, float y);
[DllImport(__DllName, EntryPoint = "get_point_distance", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float get_point_distance(Point* p);
[DllImport(__DllName, EntryPoint = "free_point", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void free_point(Point* p);
[DllImport(__DllName, EntryPoint = "call_with_callback", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int call_with_callback(delegate* unmanaged[Cdecl]<int, int> cb);
}
}
C#使用示例
// Program.cs
using System;
using System.Runtime.InteropServices;
class Program
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }]
static int CallbackMethod(int value) => value * 2;
static void Main()
{
// 创建点
var point = PointNativeMethods.create_point(3.0f, 4.0f);
try
{
// 计算距离
var distance = PointNativeMethods.get_point_distance(point);
Console.WriteLine($"Distance: {distance}"); // 应该输出5
// 使用回调
var result = PointNativeMethods.call_with_callback(&CallbackMethod);
Console.WriteLine($"Callback result: {result}"); // 应该输出84
}
finally
{
// 释放点
PointNativeMethods.free_point(point);
}
}
}
类型映射
csbindgen支持多种Rust和C#之间的类型映射:
Rust类型 | C#类型 |
---|---|
i32 |
int |
f32 |
float |
bool |
[MarshalAs(UnmanagedType.U1)]bool |
*mut T |
T* |
#[repr(C)]Struct |
[StructLayout(LayoutKind.Sequential)]Struct |
高级功能
Unity回调支持
对于Unity项目,可以配置生成使用MonoPInvokeCallback
的回调:
csbindgen::Builder::default()
.input_extern_file("src/lib.rs")
.csharp_use_function_pointer(false) // 为Unity生成兼容的回调
.generate_csharp_file("../unity/NativeMethods.cs");
库加载路径解析
internal static unsafe partial class NativeMethods
{
static NativeMethods()
{
NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver);
}
static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName == __DllName)
{
var path = "runtimes/";
var extension = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
path += "win-";
extension = ".dll";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
path += "osx-";
extension = ".dylib";
}
else
{
path += "linux-";
extension = ".so";
}
path += "/native/" + __DllName + extension;
return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);
}
return IntPtr.Zero;
}
}
总结
csbindgen提供了强大的工具来简化Rust和C#之间的互操作,支持从简单的函数调用到复杂的数据结构传递。通过自动生成绑定代码,它可以显著减少手动编写FFI代码的工作量,并帮助开发者构建高性能的跨语言应用程序。
1 回复
Rust跨语言绑定生成工具csbindgen的使用指南
介绍
csbindgen是一个专门为Rust和C#互操作设计的绑定生成工具,它能够自动生成Rust与C#之间的FFI(外部函数接口)绑定代码,简化跨语言调用的开发流程。
主要特性:
- 自动生成C#调用Rust代码所需的P/Invoke声明
- 支持Rust到C#的类型自动转换
- 生成安全的类型封装
- 简化内存管理和错误处理
- 支持各种调用约定
安装方法
将csbindgen添加到你的Cargo.toml中:
[dependencies]
csbindgen = "0.6"
或者使用cargo安装:
cargo install csbindgen
基本使用方法
1. 准备Rust代码
首先在Rust中编写需要暴露给C#的函数,使用#[no_mangle]
和extern "C"
:
// lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn get_greeting(name: *const c_char) -> *mut c_char {
let c_str = unsafe { CStr::from_ptr(name) };
let input = c_str.to_str().unwrap();
let greeting = format!("Hello, {}!", input);
let output = CString::new(greeting).unwrap();
output.into_raw()
}
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return; }
CString::from_raw(s)
};
}
2. 创建配置文件
在项目根目录创建csbindgen.toml
:
[build]
input_path = "src/lib.rs"
output_path = "bindings/generated.cs"
include_header = "// Auto-generated by csbindgen"
3. 生成绑定
运行生成命令:
csbindgen generate
这将生成C#绑定代码到指定路径。
4. 在C#中使用
生成的C#代码可以直接使用:
// C#示例
using System;
using System.Runtime.InteropServices;
class Program {
[DllImport("your_rust_library")]
public static extern int add_numbers(int a, int b);
[DllImport("your_rust_library")]
public static extern IntPtr get_greeting(string name);
[DllImport("your_rust_library")]
public static extern void free_string(IntPtr str);
static void Main() {
int sum = add_numbers(5, 7);
Console.WriteLine($"Sum: {sum}");
IntPtr greetingPtr = get_greeting("Rust");
string greeting = Marshal.PtrToStringAnsi(greetingPtr);
free_string(greetingPtr);
Console.WriteLine(greeting);
}
}
高级用法
自定义类型映射
// Rust端
#[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[no_mangle]
pub extern "C" fn create_point(x: f32, y: f32) -> Point {
Point { x, y }
}
生成的C#代码将包含对应的结构体:
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public float x;
public float y;
}
[DllImport("your_rust_library")]
public static extern Point create_point(float x, float y);
回调函数支持
// Rust端
type Callback = extern "C" fn(i32);
#[no_mangle]
pub extern "C" fn register_callback(cb: Callback) {
cb(42);
}
C#端使用:
[DllImport("your_rust_library")]
public static extern void register_callback(Action<int> callback);
// 使用
register_callback(value => {
Console.WriteLine($"Callback received: {value}");
});
完整示例demo
Rust项目结构
rust-csharp-demo/
├── Cargo.toml
├── csbindgen.toml
└── src/
└── lib.rs
Cargo.toml
[package]
name = "rust-csharp-demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
csbindgen = "0.6"
src/lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
// 简单加法函数
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
// 字符串处理函数
#[no_mangle]
pub extern "C" fn get_greeting(name: *const c_char) -> *mut c_char {
let c_str = unsafe { CStr::from_ptr(name) };
let input = c_str.to_str().unwrap();
let greeting = format!("Hello, {}!", input);
CString::new(greeting).unwrap().into_raw()
}
// 释放字符串内存
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return; }
CString::from_raw(s)
};
}
// 自定义结构体
#[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[no_mangle]
pub extern "C" fn create_point(x: f32, y: f32) -> Point {
Point { x, y }
}
// 回调函数示例
type Callback = extern "C" fn(i32);
#[no_mangle]
pub extern "C" fn register_callback(cb: Callback) {
cb(42);
}
csbindgen.toml
[build]
input_path = "src/lib.rs"
output_path = "../csharp-demo/GeneratedBindings.cs"
include_header = "// Auto-generated by csbindgen"
C#项目使用
Program.cs
using System;
using System.Runtime.InteropServices;
namespace RustCSharpDemo
{
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public float x;
public float y;
}
class Program
{
[DllImport("rust_csharp_demo")]
public static extern int add_numbers(int a, int b);
[DllImport("rust_csharp_demo")]
public static extern IntPtr get_greeting(string name);
[DllImport("rust_csharp_demo")]
public static extern void free_string(IntPtr str);
[DllImport("rust_csharp_demo")]
public static extern Point create_point(float x, float y);
[DllImport("rust_csharp_demo")]
public static extern void register_callback(Action<int> callback);
static void Main(string[] args)
{
// 基本类型示例
int sum = add_numbers(5, 7);
Console.WriteLine($"5 + 7 = {sum}");
// 字符串处理示例
IntPtr greetingPtr = get_greeting("World");
string greeting = Marshal.PtrToStringAnsi(greetingPtr);
free_string(greetingPtr);
Console.WriteLine(greeting);
// 结构体示例
Point p = create_point(1.5f, 2.5f);
Console.WriteLine($"Point created: ({p.x}, {p.y})");
// 回调函数示例
register_callback(value => {
Console.WriteLine($"Callback received value: {value}");
});
}
}
}
最佳实践
- 内存管理:Rust分配的内存应由Rust释放,反之亦然
- 错误处理:考虑使用错误码或Result类型进行跨语言错误传递
- 类型安全:尽量使用简单类型或明确标记了
#[repr(C)]
的类型 - 性能考虑:减少跨语言调用次数,批量处理数据
常见问题解决
- 类型不匹配:确保Rust和C#端使用相同大小的类型
- 调用约定:检查
extern "C"
和CallingConvention
设置 - DLL加载:确保C#能找到Rust生成的动态库
- 内存泄漏:确保成对使用分配和释放函数