如何将C#实例方法传递给Rust作为回调函数?
搞定C#实例方法传给Rust当回调的正确姿势
你已经搞定了静态方法的回调,那实例方法其实就是多了个实例指针的事儿,我给你一步步说清楚怎么弄:
1. 先理清核心逻辑:实例方法需要绑定对象
C#里的实例方法必须绑定到具体的对象实例才能执行,所以Rust那边的trigger函数得同时接收两个东西:回调的函数指针和实例的不透明指针(对应Rust的libc::c_void)。Rust触发回调时,要把这俩一起传给C#,这样C#才能找到对应的实例调用方法。
2. C#端的代码调整
首先得定义适配的委托,还要用GCHandle来固定实例(因为引用类型的内存是GC托管的,随时可能被挪位置,必须固定住才能拿到稳定的指针):
using System; using System.Runtime.InteropServices; class Program { // 定义适配Rust的委托,第一个参数是实例指针,第二个是业务参数 delegate void InstancePrintFn(IntPtr instance, int x); // 你的实例方法 public void Print(int x) { Console.WriteLine($"实例方法被调用啦:{x}"); } // 静态适配方法:把Rust传过来的实例指针转成对象,再调用实例方法 private static void InstancePrintAdapter(IntPtr instancePtr, int x) { // 从指针还原出GCHandle,再拿到对应的实例 GCHandle handle = GCHandle.FromIntPtr(instancePtr); Program obj = (Program)handle.Target; obj.Print(x); } // 导入Rust的trigger函数,现在要传两个参数:回调和实例指针 [DllImport("你的Rust库文件名.dll")] private static extern void trigger(InstancePrintFn callback, IntPtr instancePtr); public static void Main(string[] args) { // 创建你的实例 Program myInstance = new Program(); // 固定实例,防止GC把它挪走或回收,拿到对应的IntPtr GCHandle instanceHandle = GCHandle.Alloc(myInstance); IntPtr instancePtr = GCHandle.ToIntPtr(instanceHandle); // 把适配方法和实例指针传给Rust trigger(InstancePrintAdapter, instancePtr); // 划重点:用完一定要释放GCHandle,不然会内存泄漏! // 如果Rust会长期持有这个指针,就等不需要的时候再释放,比如程序退出前 // instanceHandle.Free(); } }
3. Rust端的代码调整
对应地,Rust这边要修改回调类型和trigger函数,接收实例指针并在触发回调时传回去:
use libc::{c_void, c_int}; // 定义回调类型,第一个参数是C#实例的不透明指针,第二个是业务参数 type InstancePrintCallback = extern "C" fn(*mut c_void, c_int); #[no_mangle] pub extern "C" fn trigger(callback: InstancePrintCallback, instance_ptr: *mut c_void) { // 触发回调时,把实例指针和业务参数一起传回去 callback(instance_ptr, 42); // 这里随便传个测试参数 }
4. 关键细节:怎么正确拿实例指针?
- 如果是引用类型(比如class):绝对不能直接取地址!必须用
GCHandle.Alloc固定对象,再通过GCHandle.ToIntPtr拿指针,不然GC一动对象,Rust手里的指针就失效了。 - 如果是值类型(比如struct):可以用
Marshal.StructureToPtr把结构体转成IntPtr,但也要注意内存释放,用完记得用Marshal.FreeHGlobal释放。
5. 偷懒的简化方式(适合不需要手动操作实例指针的场景)
其实C#的实例委托本身就包含了实例的引用,如果你不需要Rust知道实例指针,只是想让Rust调用实例方法,也可以这么弄:
using System; using System.Runtime.InteropServices; class Program { delegate void PrintFn(int x); [DllImport("你的Rust库文件名.dll")] private static extern void trigger(PrintFn callback); public void Print(int x) { Console.WriteLine($"实例方法被调用啦:{x}"); } public static void Main(string[] args) { Program myInstance = new Program(); // 创建实例方法的委托 PrintFn instanceDelegate = myInstance.Print; // 必须固定委托,防止GC回收它,不然Rust拿到的函数指针会失效 GCHandle delegateHandle = GCHandle.Alloc(instanceDelegate); trigger(instanceDelegate); delegateHandle.Free(); } }
这种情况下Rust的回调类型还是extern "C" fn(c_int),直接调用callback(42)就行,CLR会自动帮你绑定实例。缺点是Rust看不到实例指针,适合简单场景。
内容的提问来源于stack exchange,提问作者Shmoopy
相关产品推荐
相关产品推荐

