65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 IMessageFilter: 完整版

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (8投票s)

2006年2月12日

6分钟阅读

viewsIcon

52822

downloadIcon

537

本文将向您展示如何创建使用 IMessageFilter 的 COM 对象,包括客户端和服务器端。

引言

在我 上一篇关于 IMessageFilter 的文章 中,我描述了如何在应用程序中使用此接口。我实现了一个 IMessageFilter 接口(类 IMFImpl),并且我的 COM 对象继承自这个 IMFImpl。后来我意识到这个解决方案不够好,我需要将 IMessageFilter 用作一个独立的对象。这样做的原因(这里不相关)是我的对象的销毁。通常,在我的对象被销毁后,COM 会持有指向我消息过滤器的指针,并尝试销毁它,导致崩溃。

那么,如何将 IMessageFilter 用作一个独立的依赖对象呢?嗯,我在网上找不到任何示例,所以我决定创建自己的解决方案。我在这里尝试进行解释,以便有人可能会觉得它有用。

理解代码:服务器端

IDL

在您的 COM 对象的 IDL 文件中,添加新接口

IMyMessageFilter : IMessageFilter
{
    HRESULT block();
    HRESULT unblock();
    HRESULT registerMessageFilter();
    HRESULT unregisterMessageFilter();
     
};

这个独立的的消息过滤器知道如何注册和注销自己。registerMessageFilterunregisterMessageFilter 方法用于注册和注销消息过滤器。blockunblock 方法适用于重入情况:如果我们遇到这种情况,我们可以决定是阻止调用还是允许它进入。(对象有一个内部标志,在这些方法中设置)。然后,声明实现此接口的 coclassMyMessageFilter

coclass MyMessageFilter
{
   [default] interface IMyMessageFilter;
}

H / CPP 文件

像示例一样创建 H 和 CPP 文件。从技术上讲,我们创建了一个新的 STA 对象,它知道如何注册和注销自己。此外,它还支持两个方法,blockunblock。通过调用这些方法,客户端告诉消息过滤器如何处理有问题的调用(在处理前一个调用时进入对象 - 重入!)。

使用新的消息过滤器 COM 对象

您的 COM 对象,需要此消息过滤器的解决方案,可以通过几种方式使用此消息过滤器 COM 对象。我选择将其作为成员持有。因此,我的主 COM 对象将其作为成员持有消息过滤器 COM 对象的一个智能指针。

class ATL_NO_VTABLE CServer : 
    public CComObjectRootEx<CCOMSINGLETHREADMODEL>, 
          public CComCoClass<CSERVER &CLSID_Server,>, 
          public IServer 
{
        .....
private:
    MessageFilterServerLib::IMyMessageFilterPtr m_MessageFilter;
}

然后在构造函数中初始化此指针

CServer::CServer() 
{
    HRESULT hr = m_MessageFilter.CreateInstance(
                 __uuidof(TEAMLAYERLIBLib::TLMessageFilter));
    if(FAILED(hr))
        //do something
    hr = m_MessageFilter->raw_registerMessageFilter();
    if(FAILED(hr)) 
        //do something
    m_MessageFilter->raw_block(); 
    // ***
    //if no need to block anymore - unblock here. 
    //otherwise, keep blocking:
    m_MessageFilter->raw_unblock(); 
}

现在在析构函数中注销

CServer::~CServer() 
{
    m_MessageFilter->raw_unregisterMessageFilter();
}

理解代码:客户端

main() 函数创建服务器。这样,它就在主 STA 中创建了 COM 服务器,这是一个 STA 对象。消息过滤器仅在跨apartment 的 COM 调用期间生效。如果您在同一 apartment 内调用 COM 服务器,该调用将不会通过消息过滤器。因此,在测试程序中,您可以看到主线程创建了服务器。此外,它还创建了两个 STA 工作线程,这些工作线程进行调用。这样,我生成了跨apartment 的 COM 调用,消息过滤器就能工作了。

创建服务器对象的​​主线程必须将接口指针封送(marshal)给工作线程。这是通过 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream(简称...)完成的。

如果您查看工作线程,您会发现每个线程都注册了自己的消息过滤器。原因是每个 STA 线程都必须注册自己的过滤器。在我的测试程序中,工作线程必须注册它们的消息过滤器,然后我们才能看到过滤器的客户端和服务器端(见下文解释)。这个消息过滤器可以和服务器的相同,也可以不同。在我的示例中,这个消息过滤器与服务器的不同。想法是,一个客户端可以有许多服务器,每个服务器可能有自己的消息过滤器。客户端应该使用哪一个?另一个原因:服务器如何猜测客户端在被拒绝调用时想要做什么?这就是为什么客户端有自己的消息过滤器。这个消息过滤器是 class CClientMessageFilter 类型。

“过滤的客户端和服务器端”:如您在文献中所读到的,当消息过滤器的 IMessageFilter::HandleInComingCall() 返回 SERVERCALL_RETRYLATERSERVERCALL_REJECTED 时,COM 会调用 **客户端** 的 IMessageFIlter::RetryRejectedCall()。如果您希望您的客户端在这些情况下有特殊行为,它必须自己注册一个消息过滤器。

我在服务器线程(创建组件的那个线程)上使用消息泵。在此情况下,消息泵是 **必需** 的,您不能使用事件,例如 WaitForSingleObject(),因为主线程是 STA 线程,STA 线程不得阻塞。当它阻塞时,它无法接受传入的 COM 调用。这就是为什么您必须使用消息泵而不是“等待”函数。

重要的注意事项

  1. 服务器不必实现 IMessageFilter,它只需要注册一个实现该接口的对象。前者必须在与后者注册相同的 STA apartment 中运行。每个 STA 线程只能注册一个消息过滤器,但一个线程可以容纳任意数量的 COM 对象。
  2. 我们假设这里有三个 COM 对象
    1. 服务器。
    2. 在服务器线程中实现 IMessageFilter 并注册的对象。
    3. 在调用(客户端)线程中实现 IMessageFilter 并注册的对象。

    后两个可以是同一个 COM 对象的两个实例,也可以是两个不同的独立实现,随您决定。最后一个,即客户端的消息过滤器,无需可创建,也无需任何注册。请参阅示例代码以了解什么足够(类 CClientMessageFilter)。

  3. 重现重入的最简单方法可能是创建两个工作线程,让每个线程执行一次调用(并让被调用的对象弹出消息框)。其中一个调用会嵌套在另一个调用中。这就是我的控制台测试程序所做的。
  4. 服务器中 Foo() 的实现(消息框)会导致 CALLTYPE_TOPLEVEL 类型的重入。这意味着实际上一切正常,重入调用已准备好进行处理。我想重现出现问题的情况,即消息过滤器返回 SERVERCALL_RETRYLATERSERVERCALL_REJECTED。这种情况发生在 CALLTYPE_TOPLEVEL_CALLPENDINGCALLTYPE_NESTED 等调用类型中。通常,CALLTYPE_TOPLEVEL_CALLPENDING 仅在您收到传入的跨apartment COM 调用时发生,而此时正在进行一个传出的跨apartment COM 调用。也就是说,当接受调用的服务器也在进行其他调用时。它并非在所有可能发生重入的情况下都会出现。在您的应用程序中,**不要** 在 CALLTYPE_TOPLEVEL 的情况下返回 SERVERCALL_RETRYLATER,但在本次测试中,我想演示 SERVERCALL_RETRYLATER 的情况,因此每 X 次调用,我都会返回此值。
  5. 如果您忘记在 RGS 文件中写入 ThreadingModel=Apartment 子句,该组件将被视为旧式的单线程组件,这意味着其所有实例都必须在单个线程上创建 - 所谓的 main STA 线程。当工作线程尝试创建它时,接口需要被封送。但是 IMessageFilter 是不可封送的,因为 IMessageFilter 会挂钩到封送过程。想象一下:您正在进行一个封送的跨apartment 调用。由于某种原因,需要调用消息过滤器。但它位于另一个线程,因此对消息过滤器的调用本身也需要被封送。但是这种封送可能导致需要调用消息过滤器,而它又位于另一个 apartment。IMessageFilter 是不可封送的,因为它的方法不返回 HRESULT

结论

我关于消息过滤器的知识归功于 Igor Tandetnik。没有他的帮助,这篇文章就不可能写出来。

© . All rights reserved.