【问题标题】:Life management of std::function/std::bind (passing a functor as lparam in Windows PostMessage)std::function/std::bind 的生命周期管理(在 Windows PostMessage 中将仿函数作为 lparam 传递)
【发布时间】:2012-11-01 13:03:30
【问题描述】:

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

我想要一个通用框架,用于通过 Windows PostMessage API 进行异步执行。这是环境迫使我退出当前消息处理并在消息队列中注册任务的时候。

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

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

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

我正在通过教科书注册WM_ASYNC_TASK,并且我的Windows 程序(我实际上正在使用WTL)运行良好。 PostMessage 工作正常,消息最终将通过wparamlparam 正确并按预期到达onAsyncTask

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 时,编译器会报错,在 PostMessage 内(WPARAM 基本上是很长的)。
  2. 我可以使用f的地址:(WPARAM)&amp;f
  3. 这引发了关于 f 生命周期的另一个问题。如何保存?
  4. 当传递 &amp;f(将 f 保持在 Sender() 之外 - 这不是我的愿望)时,我在尝试取消引用 f 时在 onAsyncHandler 中遇到访问冲突。
  5. 关于生命周期,我可以将f置于std::shared_ptr的控制之下吗?
  6. 没有真正的理由将参数传递给 wparam 中的函数(在我的例子中是文本)。我认为应该有一种方法可以让 std::function 对象的文本部分,但语法失败。

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

谢谢!

【问题讨论】:

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


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

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

    1. 创建全局异步任务队列,CPluginCHiddenWindow 应该可以访问它
    2. 队列中的每个任务都应具有唯一标识符,例如清点数
    3. CPlugin::Sender() 将任务放入队列并将异步消息发布到 CHiddenWindow,任务 ID 为 WPARAM/LPARAMSender() 也将此任务 ID 保存在 CPlugin 实例中 - 如果 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 实例在接收方处理消息之前就被破坏了怎么办?在这种情况下,您将在 CHiddenWindow::onAsyncTask 内调用 f() 时出现未定义行为(很可能是访问冲突/崩溃)。

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

    【讨论】:

      猜你喜欢
      • 2015-06-28
      • 1970-01-01
      • 2013-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多