3

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

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

该框架使用指向静态函数 (in WPARAM) 的指针和指向LPARAM包含“this”指针以及其他上下文的上下文的指针 (in) 运行良好。

我想把它提升到一个新的水平并使用绑定和函数结构。我在 Visual Studio 2010 上(即std::function存在但 TR1)。

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

我正在WM_ASYNC_TASK通过教科书注册 a,并且我的 windows 程序(我实际上正在使用 WTL)运行良好。PostMessage工作正常,消息最终将onAsyncTask正确地按预期到达wparamand 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!!" );
}

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

正如我所说,我在可变参数模板之前使用 VS 2010。Microsoft 有 C++0x 的 TR1 实现。我正在寻找一个不包括 boost 的解决方案,因为它现在对我不可用。

谢谢!

4

1 回答 1

2
  1. 编译器是正确的,std::function是无法转换为的重要对象,WPARAM实际上是unsigned int.
  2. 是的,这是可能的(实际上是唯一正确的方法),但会引发正确的类型转换和生命周期管理问题。
  3. 生命周期f肯定由 管理CPlugin。当您将其绑定到成员函数时,它与CPlugin生命周期密切相关。thisCPlugin
  4. 当然,这是因为f在超出Sender()函数范围(返回时)时被销毁。由于异步消息传递,这发生在CHiddenWindow处理消息之前。InCHiddenWindow::onAsyncTask指针f已经无效。
  5. shared_ptr是强制共享所有权的好工具(这是您的情况),但您仍然无法通过WPARAM.
  6. 有什么问题std::bind(&CPlugin::ShowMessagebox, this, "Hello!");?

这种框架的可能解决方案:

  1. 创建全局异步任务队列,CPluginCHiddenWindow有权访问它
  2. 队列中的每个任务都应具有唯一的标识符,例如计数
  3. CPlugin::Sender()将任务放入队列并将异步消息发布到CHiddenWindow任务 ID 为WPARAM/LPARAM. Sender()还在CPlugin实例中保留此任务 ID - 如果CPlugin实例在对等方处理所有消息之前被销毁,则应将其从队列中删除。
  4. 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;
}

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

  1. 如果发布的消息永远不会被接收者处理,例如它会因为某种原因被关闭怎么办?在这种情况下,Func对象将被泄露,浪费你的内存。
  2. 如果CPlugin发送消息的实例在接收者处理消息之前被销毁怎么办?f()在这种情况下,您将在调用inside时出现未定义的行为(很可能是访问冲突/崩溃)CHiddenWindow::onAsyncTask

因此,只有在您有很强的保证所有已发布的消息都将被接收者处理并且发送者不会在所有已发布的消息都被处理之前被破坏的情况下,您才应该考虑这种解决方案。

于 2012-11-01T13:39:12.050 回答