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

在C#中调用C API时如何传递方法作为参数?

没问题,我来帮你搞定C#调用C API传递回调函数的事儿!其实核心是用C#的**委托(Delegate)**来对应C语言里的函数指针,下面一步步给你讲清楚怎么做:

步骤1:定义C类型对应的C#结构/枚举

首先得把C里的ll_status_t和ll_scan_result_t在C#里做对应的类型声明,保证数据能正确跨平台传递:

假设C里的ll_status_t是枚举类型,ll_scan_result_t是结构体,那C#里可以这么写:

// 对应C的ll_status_t枚举,替换成你实际的枚举值
public enum ll_status_t
{
    LL_STATUS_OK = 0,
    LL_STATUS_ERROR = 1,
    LL_STATUS_TIMEOUT = 2
    // 其他状态值...
}

// 对应C的ll_scan_result_t结构体,布局要和C完全一致
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ll_scan_result_t
{
    // 按照C结构体的字段顺序和类型逐一对应
    public int device_id;
    [MarshalAs(UnmanagedType.LPStr)] // 对应C里的char*字符串
    public string device_name;
    public int rssi;
    // 其他字段...
}
步骤2:声明对应C函数指针的委托类型

C里的函数指针在C#里要用委托来表示,我们需要声明和C回调函数签名完全匹配的委托:

// 对应C的void OnStatusRcv(ll_status_t status)
public delegate void StatusCallback(ll_status_t status);

// 对应C的void scanCb(ll_scan_result_t *result)
// 用ref关键字对应C里的结构体指针,保证类型安全
public delegate void ScanCallback(ref ll_scan_result_t result);
步骤3:导入C API函数

用DllImport特性把C库中的函数导入到C#里,注意要指定正确的库文件名和调用约定(C默认是Cdecl,如果是Windows API风格则是StdCall):

public static class NativeLLApi
{
    // 替换成你的C库文件名,比如"libll.dll"或者"ll.dll"
    private const string LibPath = "你的C库文件名.dll";

    [DllImport(LibPath, CallingConvention = CallingConvention.Cdecl)]
    public static extern void LL_SetStatusCb(StatusCallback callback);

    [DllImport(LibPath, CallingConvention = CallingConvention.Cdecl)]
    public static extern void LL_SetScanCb(ScanCallback callback);

    [DllImport(LibPath, CallingConvention = CallingConvention.Cdecl)]
    public static extern void LL_Scan();
}
步骤4:实现C#回调方法并传递

现在可以在C#里实现自己的回调逻辑,然后把委托实例传递给C API:

// 重要提示:一定要把委托实例保存为类成员/静态变量,防止被GC回收!
// 如果是局部变量,GC可能在回调触发前回收它,导致程序崩溃
private static StatusCallback _statusCallback;
private static ScanCallback _scanCallback;

public static void Main(string[] args)
{
    // 初始化委托并保存
    _statusCallback = OnStatusReceived;
    _scanCallback = OnScanResultReceived;

    // 设置回调到C API
    NativeLLApi.LL_SetStatusCb(_statusCallback);
    NativeLLApi.LL_SetScanCb(_scanCallback);

    // 启动扫描
    NativeLLApi.LL_Scan();

    // 如果是控制台程序,需要阻塞等待回调执行,否则程序直接退出
    Console.WriteLine("扫描中,按任意键退出...");
    Console.ReadKey();
}

// 实现状态回调逻辑
private static void OnStatusReceived(ll_status_t status)
{
    Console.WriteLine($"收到状态通知: {status}");
    // 你的业务逻辑处理...
}

// 实现扫描结果回调逻辑
private static void OnScanResultReceived(ref ll_scan_result_t result)
{
    Console.WriteLine($"扫描到设备: ID={result.device_id}, 名称={result.device_name}, RSSI={result.rssi}");
    // 你的业务逻辑处理...
}
关键注意事项
  • 调用约定必须匹配:如果C库用的是stdcall(比如Windows下的API),一定要把DllImport里的CallingConvention改成CallingConvention.StdCall,否则会导致栈溢出或者崩溃。
  • 委托生存期:必须把委托实例保存到不会被GC回收的地方(比如静态变量、类成员),否则GC会回收委托,导致C API回调时找不到函数地址。
  • 结构体布局一致性:C#结构体的字段顺序、类型、字节对齐必须和C完全一致,否则会出现数据错乱。可以用StructLayout的Pack属性指定对齐方式(比如Pack=1对应C的#pragma pack(1))。
  • 线程安全:如果C API是在后台线程触发回调,C#的回调方法里要注意线程安全(比如访问UI控件时需要切换到UI线程)。

内容的提问来源于stack exchange,提问作者Simon SCHÖNEGGER

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:14:52