You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:55:23