在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
相关产品推荐
相关产品推荐

