非托管C ++中的WinForm样式的Invoke()

时间:2010-04-13 03:16:15

标签: c++ multithreading user-interface winapi

我一直在为一个爱好项目玩DataBus型设计,我遇到了一个问题。后端组件需要通知UI已发生的事情。我的总线实现相对于发送者同步传递消息。换句话说,当您调用Send()时,该方法将阻塞,直到所有处理程序都已调用。 (这允许调用者对事件对象使用堆栈内存管理。)

但是,请考虑事件处理程序更新GUI以响应事件的情况。如果调用处理程序,并且消息发送者位于另一个线程上,则由于Win32的GUI元素具有线程关联性,处理程序无法更新GUI。 .NET等更动态的平台允许您通过调用特殊的Invoke()方法来处理此问题,以将方法调用(和参数)移动到UI线程。我猜他们使用.NET停车窗口等来做这些事情。

一种病态的好奇心诞生了:即使我们限制问题的范围,我们能用C ++做到这一点吗?我们能否使其比现有解决方案更好?我知道Qt与moveToThread()函数类似。

通过更好,我会提到我特别试图避免以下形式的代码:

if(! this->IsUIThread())
{
    Invoke(MainWindowPresenter::OnTracksAdded, e);
    return;
}

位于每个UI方法的顶部。在处理这个问题时,这种舞蹈在WinForms中很常见。我认为这种关注应该与特定于域的代码和用于处理它的包装器对象隔离开来。

我的实施包括:

  • DeferredFunction - 将目标方法存储在FastDelegate中的仿函数,并深度复制单个事件参数。这是跨线程边界发送的对象。

  • UIEventHandler - 负责从总线调度单个事件。调用Execute()方法时,它会检查线程ID。如果它与UI线程ID(在构造时设置)不匹配,则在堆上使用实例,方法和事件参数分配DeferredFunction。指向它的指针通过PostThreadMessage()发送到UI线程。

  • 最后,线程消息泵的钩子函数用于调用DeferredFunction并取消分配它。或者,我可以使用消息循环过滤器,因为我的UI框架(WTL)支持它们。

归根结底,这是一个好主意吗?整个消息挂钩的事情让我很伤心。意图当然是高尚的,但我是否应该知道任何陷阱?或者有更简单的方法吗?

2 个答案:

答案 0 :(得分:3)

我已经离开Win32游戏很长一段时间了,但我们过去实现这个目的的方式是使用PostMessage将一个Windows消息发回UI线程,然后从那里处理调用,传递额外的wParam / lParam中需要的信息。

事实上,如果.NET在Control.Invoke中处理这个问题,我不会感到惊讶。

更新:我是currios所以我检查了反射器,这就是我找到的。

Control.Invoke调用MarshaledInvoke,它会执行一系列检查等,但有趣的调用是RegisterWindowMessage和PostMessage。事情并没有那么大改变:)

答案 1 :(得分:1)

一些后续信息:

有几种方法可以做到这一点,每种方法都有优点和缺点:

  • 最简单的方法可能就是QueueUserAPC()来电。 APC有点太深入无法解释,但唯一的缺点是如果线程意外地进入可警告的等待状态,它们可能会在你没有准备好时运行。因此,我避免了它们。对于简短的应用程序,这可能没问题。

  • 第二种方法涉及使用PostThreadMessage(),如前所述。这比QueueUserAPC()好,因为你的回调对处于可警告等待状态的UI线程不敏感,但是使用这个API会导致你的回调根本没有运行。见Raymond Chen's discussion on this。要解决这个问题,你需要在线程的消息队列中放一个钩子。

  • 第三种方法是设置一个不可见的仅消息窗口,其WndProc调用延迟调用,并使用PostMessage()作为回调数据。因为它是针对特定窗口的,所以消息不会在模态UI情况下被吃掉。此外,仅消息窗口不受系统消息广播的影响(从而防止消息ID冲突)。缺点是它需要比其他选项更多的代码。