在COM编程中,单线程公寓(STA)对象可能会遇到重入问题,即当一个线程正在执行某个操作时,另一个线程尝试调用该对象的方法,可能会导致不可预知的行为。为了解决这个问题,可以使用IMessageFilter接口。IMessageFilter接口允许COM对象决定是否接受或拒绝传入的调用。本文将介绍如何实现IMessageFilter接口,并提供一个具体的示例。
IMessageFilter接口定义了三个方法:HandleInComingCall、RetryRejectedCall和MessagePending。这些方法用于处理传入的调用请求。当一个调用请求到达时,COM会调用HandleInComingCall方法,该方法可以返回以下几种值:
如果HandleInComingCall返回SERVERCALL_REJECTED,COM会调用RetryRejectedCall方法,以确定是否应该取消调用或稍后重试。如果HandleInComingCall返回SERVERCALL_RETRYLATER,COM会调用MessagePending方法,以确定是否应该处理挂起的消息。
要实现IMessageFilter接口,首先需要创建一个继承自IMessageFilter的类。以下是一个简单的实现示例:
class ATL_NO_VTABLE CFoo :
public CComObjectRootEx,
public IMessageFilterImpl, ...
{
public:
CFoo() {}
BEGIN_COM_MAP(CFoo)
COM_INTERFACE_ENTRY(IFoo)
COM_INTERFACE_ENTRY(IMessageFilter) ...
END_COM_MAP()
HRESULT FinalConstruct() {
return RegisterFilter();
}
DWORD ProcessInComingCall(DWORD dwCallType, HTASK threadIDCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) {
if (dwCallType == CALLTYPE_TOPLEVEL) {
return SERVERCALL_ISHANDLED;
} else {
return SERVERCALL_REJECTED;
}
}
};
在这个示例中,CFoo类继承自IMessageFilterImpl,这是一个模板类,用于实现IMessageFilter接口。CFoo类还实现了ProcessInComingCall方法,该方法用于处理传入的调用请求。
在CFoo类的构造函数中,调用RegisterFilter方法将当前对象注册为消息过滤器。这可以通过调用CoRegisterMessageFilter函数实现。
HRESULT RegisterFilter() {
return ::CoRegisterMessageFilter(static_cast(this), NULL);
}
注册消息过滤器后,当有新的调用请求到达时,COM会调用CFoo对象的HandleInComingCall方法。
HandleInComingCall方法用于处理传入的调用请求。在这个方法中,可以根据调用类型和其他参数决定是否接受或拒绝调用。以下是一个简单的示例:
STDMETHODIMP_(DWORD) HandleInComingCall(DWORD dwCallType, HTASK threadIDCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) {
if (dwCallType == CALLTYPE_ASYNC_CALLPENDING || dwCallType == CALLTYPE_ASYNC) {
return SERVERCALL_ISHANDLED;
}
T* pT = static_cast(this);
return pT->ProcessInComingCall(dwCallType, threadIDCaller, dwTickCount, lpInterfaceInfo);
}
在这个示例中,如果调用类型是CALLTYPE_ASYNC_CALLPENDING或CALLTYPE_ASYNC,表示调用可以被接受。否则,调用将被拒绝。
如果HandleInComingCall方法返回SERVERCALL_REJECTED,COM会调用RetryRejectedCall方法。在这个方法中,可以根据需要决定是否取消调用或稍后重试。以下是一个简单的示例:
STDMETHODIMP_(DWORD) RetryRejectedCall(HTASK threadIDCallee, DWORD dwTickCount, DWORD dwRejectType) {
if (dwRejectType == SERVERCALL_REJECTED) {
return -1; // 表示取消调用
}
return dwTimeOut; // 表示稍后重试
}
在这个示例中,如果调用被拒绝,RetryRejectedCall方法返回-1,表示取消调用。否则,返回超时时间,表示稍后重试。
如果HandleInComingCall方法返回SERVERCALL_RETRYLATER,COM会调用MessagePending方法。在这个方法中,可以根据需要决定是否处理挂起的消息。以下是一个简单的示例:
STDMETHODIMP_(DWORD) MessagePending(HTASK threadIDCallee, DWORD dwTickCount, DWORD dwPendingType) {
return PENDINGMSG_WAITNOPROCESS; // 表示不处理挂起的消息
}