C++ PCIe板卡DLL的C#封装:中断监听与跨语言事件触发咨询
问题解答
1. 持续监听板卡中断:用工作线程替代主线程无限循环
直接在主线程写无限循环会阻塞整个程序的UI或业务逻辑,正确方案是在C++ DLL中创建独立工作线程,线程内部通过WaitForSingleObject循环等待中断事件。
核心实现逻辑:
- 在DLL中新增启动监听的导出接口,负责创建工作线程,并传入中断事件句柄、回调函数指针及退出信号事件。
- 工作线程内循环流程:
- 同时监听退出信号和中断事件,优先响应退出指令
- 若捕获到中断事件(
WaitForSingleObject返回WAIT_OBJECT_0),则处理中断逻辑并调用回调通知C# - 重置手动中断事件(因你创建的是手动重置事件,触发后会保持信号态,必须调用
ResetEvent才能继续等待下一次中断) - 若未收到退出信号,回到步骤1继续等待
C++ DLL核心代码示例:
#include <windows.h> // 定义回调函数类型,用于通知C#中断触发 typedef void(__stdcall *InterruptCallback)(void); // 线程参数结构体:保存中断事件、回调、退出信号 typedef struct { HANDLE hInterruptEvent; InterruptCallback callback; HANDLE hQuitEvent; } ThreadParams; // 工作线程函数 DWORD WINAPI InterruptListenThread(LPVOID lpParam) { ThreadParams* pParams = (ThreadParams*)lpParam; HANDLE hEvents[2] = { pParams->hQuitEvent, pParams->hInterruptEvent }; while (true) { // 同时等待退出信号和中断事件,INFINITE表示无超时等待 DWORD dwWaitResult = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); if (dwWaitResult == WAIT_OBJECT_0) { // 收到退出信号,终止线程 break; } else if (dwWaitResult == WAIT_OBJECT_0 + 1) { // 触发中断,调用回调通知C# if (pParams->callback != NULL) { pParams->callback(); } // 重置中断事件,准备下一次等待 ResetEvent(pParams->hInterruptEvent); } } delete pParams; return 0; } // 导出接口:启动中断监听 extern "C" __declspec(dllexport) HANDLE __stdcall StartInterruptListen(HANDLE hInterruptEvent, InterruptCallback callback) { ThreadParams* pParams = new ThreadParams(); pParams->hInterruptEvent = hInterruptEvent; pParams->callback = callback; // 创建退出事件,用于优雅终止线程 pParams->hQuitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建工作线程 return CreateThread(NULL, 0, InterruptListenThread, pParams, 0, NULL); } // 导出接口:停止中断监听 extern "C" __declspec(dllexport) void __stdcall StopInterruptListen(HANDLE hThread, HANDLE hQuitEvent) { SetEvent(hQuitEvent); // 等待线程退出,超时5秒强制结束 WaitForSingleObject(hThread, 5000); // 释放资源 CloseHandle(hThread); CloseHandle(hQuitEvent); }
2. 高效触发C#事件:通过委托回调实现
C#可通过委托将事件处理函数传递给C++ DLL,当中断触发时,C++直接调用该委托的函数指针,从而触发C#中的事件,这是跨语言回调最高效的方式之一。
关键注意事项:
- 委托签名必须与C++回调函数类型完全匹配(调用约定为
__stdcall) - 必须固定委托内存,避免被.NET GC回收,否则会导致程序崩溃
C#核心代码示例:
using System; using System.Runtime.InteropServices; public class PcieCardListener { // 定义与C++回调匹配的委托 [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void InterruptCallback(); // 导入C++ DLL接口 [DllImport("YourPcieDll.dll", CallingConvention = CallingConvention.StdCall)] private static extern IntPtr StartInterruptListen(IntPtr hInterruptEvent, InterruptCallback callback); [DllImport("YourPcieDll.dll", CallingConvention = CallingConvention.StdCall)] private static extern void StopInterruptListen(IntPtr hThread, IntPtr hQuitEvent); // 固定委托,防止GC回收 private static GCHandle _callbackHandle; private static InterruptCallback _interruptCallback; private IntPtr _listenThreadHandle; private IntPtr _quitEventHandle; // 对外暴露的C#中断事件 public event EventHandler InterruptReceived; public void StartListening(IntPtr hInterruptEvent) { // 初始化委托并固定内存 _interruptCallback = OnInterruptTriggered; _callbackHandle = GCHandle.Alloc(_interruptCallback); // 启动监听线程(实际需根据DLL实现调整退出事件句柄的获取逻辑) _listenThreadHandle = StartInterruptListen(hInterruptEvent, _interruptCallback); } public void StopListening() { if (_listenThreadHandle != IntPtr.Zero) { StopInterruptListen(_listenThreadHandle, _quitEventHandle); _listenThreadHandle = IntPtr.Zero; } // 释放委托的固定内存 if (_callbackHandle.IsAllocated) { _callbackHandle.Free(); } } // 委托对应的处理函数,触发C#事件 private void OnInterruptTriggered() { // 若涉及UI操作,需切换到UI线程(WinForms用Invoke,WPF用Dispatcher.Invoke) InterruptReceived?.Invoke(this, EventArgs.Empty); } }
额外注意点
- 事件重置:因你创建的是手动重置事件(
CreateEvent第二个参数为TRUE),中断触发后必须调用ResetEvent,否则WaitForSingleObject会一直返回成功,导致无限触发回调。 - 线程安全:C#事件触发若涉及UI更新,必须切换到UI线程执行,避免跨线程操作异常。
- 资源清理:程序退出或停止监听时,必须调用
StopInterruptListen释放线程和事件句柄,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Danilo7
相关产品推荐
相关产品推荐

