如何通过中间函数将C++类成员函数设为BindIoCompletionCallback的回调
解决方案
Windows 系统API BindIoCompletionCallback 要求传入的回调必须是符合指定签名的原生C函数,类的非静态成员函数隐含this指针参数,和API要求的签名不匹配;std::bind返回的是带状态的可调用对象,无法直接转换为原生函数指针供系统API调用。可以通过静态中间函数+扩展OVERLAPPED结构体的方案实现需求,具体实现如下:
步骤1:自定义扩展OVERLAPPED结构体
在原生OVERLAPPED结构体基础上扩展类实例指针字段,用来传递当前foo对象的地址,注意必须将原生OVERLAPPED放在结构体最开头保证类型转换安全:
// 扩展的OVERLAPPED结构体 struct MyOverlapped : OVERLAPPED { foo* pInstance; // 可按需扩展其他自定义字段 };
步骤2:修改foo类的回调定义
新增静态成员函数作为中间转发层(符合API要求的回调签名),同时新增非静态成员函数处理实际业务逻辑:
class foo { public: void Work(); // 处理IO完成逻辑的非静态成员函数,可正常访问类内所有成员 void HandleIOCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped); // 静态中间回调函数,符合BindIoCompletionCallback要求的签名 static VOID CALLBACK StaticIOCompletionCallback(_In_ DWORD dwErrorCode, _In_ DWORD dwNumberOfBytesTransfered, _Inout_ LPOVERLAPPED lpOverlapped); };
步骤3:实现静态中间函数的转发逻辑
从传入的OVERLAPPED指针转换为自定义的扩展结构体,取出foo实例指针后转发到非静态业务处理函数:
VOID CALLBACK foo::StaticIOCompletionCallback(_In_ DWORD dwErrorCode, _In_ DWORD dwNumberOfBytesTransfered, _Inout_ LPOVERLAPPED lpOverlapped) { // 安全转换为自定义扩展结构体 MyOverlapped* pMyOverlapped = static_cast<MyOverlapped*>(lpOverlapped); foo* pThis = pMyOverlapped->pInstance; // 转发到实际业务处理函数 pThis->HandleIOCompletion(dwErrorCode, dwNumberOfBytesTransfered, lpOverlapped); }
步骤4:调整绑定回调和发起IO请求的逻辑
绑定回调时传入静态中间函数的地址,发起异步IO请求时使用自定义的MyOverlapped结构体,同时给实例指针字段赋值当前对象的this指针:
void foo::Work() { // 绑定回调时传入静态中间函数 if (!BindIoCompletionCallback(hnd, StaticIOCompletionCallback, 0)) { printf("Error (error code: %u)\n", GetLastError()); return; } // 发起异步IO请求时使用自定义的MyOverlapped MyOverlapped* pOverlapped = new MyOverlapped(); ZeroMemory(pOverlapped, sizeof(MyOverlapped)); pOverlapped->pInstance = this; // 存入当前类实例指针 // 此处调用ReadFile/WriteFile等异步IO接口,传入上述pOverlapped即可 // ...... } // 实际业务逻辑实现 void foo::HandleIOCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { // 此处可正常访问foo类的所有成员变量和方法 // ...... // 处理完成后释放自定义MyOverlapped的内存 delete static_cast<MyOverlapped*>(lpOverlapped); }
注意事项
- 自定义扩展OVERLAPPED结构体必须将原生OVERLAPPED放在结构体最开头,保证类型转换不会出错
- 异步IO请求生命周期需要做好管理,MyOverlapped的内存必须在IO完成回调处理结束后再释放,禁止提前销毁
- 如果不需要类内封装,也可以用全局函数代替静态成员函数作为中间层,实现原理完全一致
内容的提问来源于stack exchange,提问作者James Franco
相关产品推荐
相关产品推荐

