这似乎只是一个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可以正常工作,消息最终将正确且按预期到达onAsyncTaskwparamlparam

const UINT WM_ASYNC_TASK = RegisterWindowMessage(L"Async-Task");

我有一个CPlugin,这是我要发送发送任务的位置。我有CHiddenWindow,它是获取消息的Window实现。我希望onAsyncTask将功能转发回CPluginhwnd是窗口的句柄。

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(将f保留在Sender()之外-不是我的愿望)时,尝试取消引用onAsyncHandler时,我在f中遇到访问冲突。
  • 关于生命周期,我可以以某种方式将f置于std::shared_ptr的控制之下吗?
  • 没有真正的理由将参数传递给wparam中的函数(在我的情况下为文本)。我认为应该有一种方法让std::function对象的文本部分出现,但是语法失败。

  • 就像我说的那样,我在可变参数模板之前使用VS 2010。 Microsoft具有C++ 0x的TR1实现。我正在寻找不包含增强功能的解决方案,因为该功能目前对我不可用。

    谢谢!

    最佳答案

  • 编译器是正确的,std::function是非平凡的对象,无法转换为实际上是WPARAMunsigned int
  • 是的,这是可行的(实际上是唯一正确的方法),但是会引发正确的类型转换和生命周期管理问题。
  • f的生存时间一定要由CPlugin管理。当您将它绑定(bind)到CPluginthis成员函数时,它与CPlugin的生存时间密切相关。
  • 当然,这是因为f在超出Sender()函数的范围时(返回时)被破坏了。由于异步消息传递,这种情况发生在CHiddenWindow处理消息之前。在CHiddenWindow::onAsyncTask中,指向f的指针已经无效。
  • shared_ptr是执行共享所有权的好工具(这是您的情况),但是您仍然无法通过WPARAM传递它。
  • std::bind(&CPlugin::ShowMessagebox, this, "Hello!")有什么问题?

  • 此类框架的可能解决方案:
  • 创建全局异步任务队列,CPluginCHiddenWindow应有权访问它
  • 队列中的每个任务应具有唯一的标识符,例如点算数字
  • CPlugin::Sender()将任务放入队列,并将异步消息发布到CHiddenWindow,任务ID为WPARAM/LPARAMSender()还将此任务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时,您将具有未定义的行为(很可能是访问冲突/崩溃)。

  • 因此,只有在您有力保证在处理所有已发布的消息之前,所有已发布的消息都将由接收方处理并且发件人不会被销毁的情况下,才应考虑这种解决方案。

    08-16 00:23