这似乎只是一个Windows问题,但我的问题的实质实际上是C++ 11(或MS C++ 0x TR1)。它与传递std::function
对象及其生命周期有关。
我想拥有一个通过Windows PostMessage
API异步执行的通用框架。这是当情况迫使我退出当前消息处理并将任务注册到消息队列中时。
该框架可以很好地使用静态函数的指针(在WPARAM
中)和上下文的指针(在LPARAM
中),该上下文包含一个“this”指针以及其他上下文。
我想将其带入下一个层次,并使用bind和function结构。我使用的是Visual Studio 2010(即std::function
存在,但TR1)。
到目前为止,这就是我要做的:
我正在通过教科书注册WM_ASYNC_TASK
a,并且我的Windows过程(实际上是在使用WTL)工作正常。 PostMessage
可以正常工作,消息最终将正确且按预期到达onAsyncTask
和wparam
的lparam
。
const UINT WM_ASYNC_TASK = RegisterWindowMessage(L"Async-Task");
我有一个
CPlugin
,这是我要发送发送任务的位置。我有CHiddenWindow
,它是获取消息的Window实现。我希望onAsyncTask
将功能转发回CPlugin
。 hwnd
是窗口的句柄。在
CPlugin
中,我已经:void CPlugin::ShowMessageBox( void* arg ) {
wchar_t* text = (wchar_t*)arg;
MessageBox( NULL, text, L"Title", MB_OK )
}
void CPlugin::Sender( ) {
std::function<void(void*)> f = std::bind( &CPlug::ShowMessagebox,
this,
std::placeholders::_1 );
PostMessage( hwnd, WM_ASYNC_TASK, (WPARAM)f, (LPARAM)"Hello!!" );
}
在CHiddenWindow中,WM_ASYNC_TASK消息的处理程序中:
void CHiddinWindow::onAsyncTask( WPARAM wparam, LPARAM lparam, /* more arguments */ )
{
std::function<void(void*)> f = (std::function<void(void*)>)wparam;
void* arg = lparam;
f( arg );
}
问题:
PostMessage
内将f转换为WPARAM时,编译器会抱怨(WPARAM
基本上很长)。 f
的地址:(WPARAM)&f
。 &f
(将f
保留在Sender()
之外-不是我的愿望)时,尝试取消引用onAsyncHandler
时,我在f
中遇到访问冲突。 std::shared_ptr
的控制之下吗? std::function
对象的文本部分出现,但是语法失败。 就像我说的那样,我在可变参数模板之前使用VS 2010。 Microsoft具有C++ 0x的TR1实现。我正在寻找不包含增强功能的解决方案,因为该功能目前对我不可用。
谢谢!
最佳答案
std::function
是非平凡的对象,无法转换为实际上是WPARAM
的unsigned int
。 f
的生存时间一定要由CPlugin
管理。当您将它绑定(bind)到CPlugin
和this
成员函数时,它与CPlugin
的生存时间密切相关。 f
在超出Sender()
函数的范围时(返回时)被破坏了。由于异步消息传递,这种情况发生在CHiddenWindow
处理消息之前。在CHiddenWindow::onAsyncTask
中,指向f
的指针已经无效。 shared_ptr
是执行共享所有权的好工具(这是您的情况),但是您仍然无法通过WPARAM
传递它。 std::bind(&CPlugin::ShowMessagebox, this, "Hello!")
有什么问题? 此类框架的可能解决方案:
CPlugin
和CHiddenWindow
应有权访问它CPlugin::Sender()
将任务放入队列,并将异步消息发布到CHiddenWindow
,任务ID为WPARAM/LPARAM
。 Sender()
还将此任务ID保留在CPlugin
实例中-如果CPlugin
实例在对等方处理所有消息之前被销毁,则应将其从队列中删除。 CHiddenWindow::onAsyncTask
获取任务ID,在队列中搜索相应的任务,如果找到任务,则运行任务函子。然后它将任务从队列中删除。 当然,您可以使用一些简单(错误的)解决方案,例如:
typedef std::function<void(void*)> Func;
void CPlugin::Sender()
{
Func* f = new Func(std::bind(&CPlugin::ShowMessagebox, this, "Hello!"));
PostMessage(hwnd, WM_ASYNC_TASK, (WPARAM)f, 0);
}
void CHiddenWindow::onAsyncTask(WPARAM wparam, LPARAM lparam)
{
Func* f = (Func*)wparam;
(*f)();
delete f;
}
但是此解决方案有两个缺陷:
Func
对象将被泄漏,浪费您的内存。 CPlugin
实例在接收者处理邮件之前被销毁了怎么办?在这种情况下,在f()
中调用CHiddenWindow::onAsyncTask
时,您将具有未定义的行为(很可能是访问冲突/崩溃)。 因此,只有在您有力保证在处理所有已发布的消息之前,所有已发布的消息都将由接收方处理并且发件人不会被销毁的情况下,才应考虑这种解决方案。