如何使用 IMessageFilter: 完整版





4.00/5 (8投票s)
2006年2月12日
6分钟阅读

52822

537
本文将向您展示如何创建使用 IMessageFilter 的 COM 对象,包括客户端和服务器端。
引言
在我 上一篇关于 IMessageFilter 的文章 中,我描述了如何在应用程序中使用此接口。我实现了一个 IMessageFilter
接口(类 IMFImpl
),并且我的 COM 对象继承自这个 IMFImpl
。后来我意识到这个解决方案不够好,我需要将 IMessageFilter
用作一个独立的对象。这样做的原因(这里不相关)是我的对象的销毁。通常,在我的对象被销毁后,COM 会持有指向我消息过滤器的指针,并尝试销毁它,导致崩溃。
那么,如何将 IMessageFilter
用作一个独立的依赖对象呢?嗯,我在网上找不到任何示例,所以我决定创建自己的解决方案。我在这里尝试进行解释,以便有人可能会觉得它有用。
理解代码:服务器端
IDL
在您的 COM 对象的 IDL 文件中,添加新接口
IMyMessageFilter : IMessageFilter { HRESULT block(); HRESULT unblock(); HRESULT registerMessageFilter(); HRESULT unregisterMessageFilter(); };
这个独立的的消息过滤器知道如何注册和注销自己。registerMessageFilter
和 unregisterMessageFilter
方法用于注册和注销消息过滤器。block
和 unblock
方法适用于重入情况:如果我们遇到这种情况,我们可以决定是阻止调用还是允许它进入。(对象有一个内部标志,在这些方法中设置)。然后,声明实现此接口的 coclass
(MyMessageFilter
)
coclass MyMessageFilter
{
[default] interface IMyMessageFilter;
}
H / CPP 文件
像示例一样创建 H 和 CPP 文件。从技术上讲,我们创建了一个新的 STA 对象,它知道如何注册和注销自己。此外,它还支持两个方法,block
和 unblock
。通过调用这些方法,客户端告诉消息过滤器如何处理有问题的调用(在处理前一个调用时进入对象 - 重入!)。
使用新的消息过滤器 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)给工作线程。这是通过 CoMarshalInterThreadInterfaceInStream
和 CoGetInterfaceAndReleaseStream
(简称...)完成的。
如果您查看工作线程,您会发现每个线程都注册了自己的消息过滤器。原因是每个 STA 线程都必须注册自己的过滤器。在我的测试程序中,工作线程必须注册它们的消息过滤器,然后我们才能看到过滤器的客户端和服务器端(见下文解释)。这个消息过滤器可以和服务器的相同,也可以不同。在我的示例中,这个消息过滤器与服务器的不同。想法是,一个客户端可以有许多服务器,每个服务器可能有自己的消息过滤器。客户端应该使用哪一个?另一个原因:服务器如何猜测客户端在被拒绝调用时想要做什么?这就是为什么客户端有自己的消息过滤器。这个消息过滤器是 class CClientMessageFilter
类型。
“过滤的客户端和服务器端”:如您在文献中所读到的,当消息过滤器的 IMessageFilter::HandleInComingCall()
返回 SERVERCALL_RETRYLATER
或 SERVERCALL_REJECTED
时,COM 会调用 **客户端** 的 IMessageFIlter::RetryRejectedCall()
。如果您希望您的客户端在这些情况下有特殊行为,它必须自己注册一个消息过滤器。
我在服务器线程(创建组件的那个线程)上使用消息泵。在此情况下,消息泵是 **必需** 的,您不能使用事件,例如 WaitForSingleObject()
,因为主线程是 STA 线程,STA 线程不得阻塞。当它阻塞时,它无法接受传入的 COM 调用。这就是为什么您必须使用消息泵而不是“等待”函数。
重要的注意事项
- 服务器不必实现
IMessageFilter
,它只需要注册一个实现该接口的对象。前者必须在与后者注册相同的 STA apartment 中运行。每个 STA 线程只能注册一个消息过滤器,但一个线程可以容纳任意数量的 COM 对象。 - 我们假设这里有三个 COM 对象
- 服务器。
- 在服务器线程中实现
IMessageFilter
并注册的对象。 - 在调用(客户端)线程中实现
IMessageFilter
并注册的对象。
后两个可以是同一个 COM 对象的两个实例,也可以是两个不同的独立实现,随您决定。最后一个,即客户端的消息过滤器,无需可创建,也无需任何注册。请参阅示例代码以了解什么足够(类
CClientMessageFilter
)。 - 重现重入的最简单方法可能是创建两个工作线程,让每个线程执行一次调用(并让被调用的对象弹出消息框)。其中一个调用会嵌套在另一个调用中。这就是我的控制台测试程序所做的。
- 服务器中
Foo()
的实现(消息框)会导致CALLTYPE_TOPLEVEL
类型的重入。这意味着实际上一切正常,重入调用已准备好进行处理。我想重现出现问题的情况,即消息过滤器返回SERVERCALL_RETRYLATER
或SERVERCALL_REJECTED
。这种情况发生在CALLTYPE_TOPLEVEL_CALLPENDING
或CALLTYPE_NESTED
等调用类型中。通常,CALLTYPE_TOPLEVEL_CALLPENDING
仅在您收到传入的跨apartment COM 调用时发生,而此时正在进行一个传出的跨apartment COM 调用。也就是说,当接受调用的服务器也在进行其他调用时。它并非在所有可能发生重入的情况下都会出现。在您的应用程序中,**不要** 在CALLTYPE_TOPLEVEL
的情况下返回SERVERCALL_RETRYLATER
,但在本次测试中,我想演示SERVERCALL_RETRYLATER
的情况,因此每 X 次调用,我都会返回此值。 - 如果您忘记在 RGS 文件中写入 ThreadingModel=Apartment 子句,该组件将被视为旧式的单线程组件,这意味着其所有实例都必须在单个线程上创建 - 所谓的 main STA 线程。当工作线程尝试创建它时,接口需要被封送。但是
IMessageFilter
是不可封送的,因为IMessageFilter
会挂钩到封送过程。想象一下:您正在进行一个封送的跨apartment 调用。由于某种原因,需要调用消息过滤器。但它位于另一个线程,因此对消息过滤器的调用本身也需要被封送。但是这种封送可能导致需要调用消息过滤器,而它又位于另一个 apartment。IMessageFilter
是不可封送的,因为它的方法不返回HRESULT
。
结论
我关于消息过滤器的知识归功于 Igor Tandetnik。没有他的帮助,这篇文章就不可能写出来。