std :: function / std :: bind的生命管理(在Windows PostMessage中将仿函数作为lparam传递)

时间:2012-11-01 13:03:30

标签: c++ visual-studio-2010 winapi visual-c++ c++11

这似乎只是一个Windows问题,但我的问题的本质实际上是C ++ 11(或MS C ++ 0x TR1)。它是关于传递std::function个对象及其生命周期。

我想通过Windows PostMessage API建立一个通用的异步执行框架。这种情况迫使我退出当前的消息处理并在消息队列中注册任务。

框架工作正常,指向静态函数(在WPARAM中)和指向上下文的指针(在LPARAM中),其中包含'this'指针以及其他上下文。

我想将它带到下一级并使用绑定和函数结构。我在Visual Studio 2010上(即std::function存在,但TR1)。

到目前为止,这就是我所说的:

我正在通过教科书注册WM_ASYNC_TASK a,我的Windows程序(我实际上正在使用WTL)工作正常。 PostMessage工作正常,邮件最终会onAsyncTaskwparam lparam正确且符合预期。

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!!" );
}

在WM_ASYNC_TASK消息处理程序的CHiddenWindow中我已经:

void CHiddinWindow::onAsyncTask( WPARAM wparam, LPARAM lparam, /* more arguments */ )
{
    std::function<void(void*)> f = (std::function<void(void*)>)wparam;
    void* arg = lparam;
    f( arg );
}

问题

  1. 目前,编译器在我尝试将f转换为WPARAM时抱怨,PostMessageWPARAM基本上很长。)
  2. 如果我使用f的地址(WPARAM)&f,我很好。
  3. 提出关于f的生命周期的另一个问题。我该怎么做?
  4. 在传递&f时(将f保留在Sender()之外 - 不是我的愿望),我在onAsyncHandler尝试解除引用时遇到访问权限{{1} }。
  5. 关于生命周期,我可以将f置于f以某种方式控制吗?
  6. 没有真正的理由将参数传递给wparam中的函数(在我的例子中 - 文本)。我认为应该有一种方法可以使std::shared_ptr对象的文本部分,但失败的语法。
  7. 正如我所说,我在变量模板之前使用VS 2010。 Microsoft具有C ++ 0x的TR1实现。我正在寻找一种不包括增强功能的解决方案,因为它现在不适用于我。

    谢谢!

1 个答案:

答案 0 :(得分:2)

  1. 编译器是正确的,std::function是非平凡的对象,无法转换为实际为WPARAM的{​​{1}}。
  2. 是的,这是可能的(实际上是单一的正确方法),但会引发正确的类型转换和生命周期管理问题。
  3. unsigned int的续航时间绝对由f管理。当它与CPluginCPlugin成员函数绑定时,它与this生命周期密切相关。
  4. 当然,那是因为CPlugin在超出f函数范围时(当它返回时)被销毁。由于异步消息传递,这在Sender()处理消息之前发生。 CHiddenWindow指向CHiddenWindow::onAsyncTask的指针已无效。
  5. f是强制执行共享所有权的好工具(这是您的情况)但您仍无法通过shared_ptr传递。
  6. WPARAM;?
  7. 出了什么问题

    此类框架的可能解决方案:

    1. 创建全局异步任务队列,std::bind(&CPlugin::ShowMessagebox, this, "Hello!")CPlugin可以访问它
    2. 队列中的每个任务都应具有唯一标识符,例如计数
    3. CHiddenWindow将任务置于队列中,并将异步消息发布到CPlugin::Sender(),任务ID为CHiddenWindowWPARAM/LPARAM还将此任务ID保留在Sender()实例中 - 如果CPlugin实例在对等方处理完所有消息之前被销毁,则应将其从队列中删除。
    4. CPlugin获取任务ID,在队列中搜索相应的任务,并在找到任务时运行任务函子。然后它从队列中删除任务。
    5. 当然,您可以使用一些简单(和错误)的解决方案,例如:

      CHiddenWindow::onAsyncTask

      但是这个解决方案有两个缺陷:

      1. 如果发布的消息永远不会被接收者处理,例如它会因某种原因被关闭吗?在这种情况下,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; } 对象将被泄露,浪费你的记忆。
      2. 如果发送邮件的Func实例在接收者处理邮件之前被销毁,该怎么办?在这种情况下,您将在CPlugin内调用f()时遇到未定义的行为(很可能是访问冲突/崩溃)。
      3. 因此,只有在您有充分保证所有发布的消息都将由接收方处理且发送方在处理完所有发布的消息之前不会被销毁时,您才会考虑此类解决方案。