A Server-Side Message Filter: EKoala3

A server's message filter allows it to block incoming calls if the server (as a whole) or one or more of its objects are in some state that would force them to either reject calls completely or specify that a client should try again later. For example, a server might be in the middle of a calculation using data that a client can modify through some object's interfaces—it would be best if that data didn't change during the calculation!

IMessageFilter::HandleInComingCall is the single entry point on the server side for all incoming calls, regardless of which object and which interface those calls are headed toward. But to see the calls as they happen, the server has to implement, instantiate, and register a message filter. EKoala3 accomplishes this through a C++ class, CMessageFilter, defined in EKOALA3.H (singly inheriting from IMessageFilter) and implemented in MSGFILT.CPP. This message filter (which needs no CLSID) has its own reference count and deletes itself on the final Release call, just as any other simple object does. The only interesting part of the entire object is the HandleInComingCall function; the other two members simply return default values because they are not used in a server that makes no outgoing calls:


//m_pApp set in CMessageFilter constructor

STDMETHODIMP_(DWORD) CMessageFilter::HandleInComingCall
(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount
, LPINTERFACEINFO pInterfaceInfo)
{
if (m_pApp->m_fBlock)
return SERVERCALL_REJECTED;

if (m_pApp->m_fDelay)
return SERVERCALL_RETRYLATER;

return SERVERCALL_ISHANDLED;
}

//These functions are not used unless you make outgoing calls.
STDMETHODIMP_(DWORD) CMessageFilter::RetryRejectedCall
(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
{
return 0;
}

STDMETHODIMP_(DWORD) CMessageFilter::MessagePending
(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
return PENDINGMSG_WAITDEFPROCESS;
}

Again, the LPINTERFACEINFO structure passed to HandleInComingCall identifies which interface member (the iid and wMethod fields) of which object (the pUnk field) is being called. EKoala3's implementation of this function, however, doesn't care which object or which interface member is being called; instead, it checks its CApp variables m_fBlock and m_fDelay, which EKoala3 toggles through the Block and Delay items on its Incoming Call menu.

When you tell EKoala3 to block calls, it rejects everything that comes into HandleInComingCall by immediately returning SERVERCALL_REJECTED. This generates an RPC_E_CALL_REJECTED error in the client making the call. If you tell EKoala3 to delay calls, it returns SERVERCALL_RETRYLATER. Both these return values end up in the client message filter's RetryRejectedCall function, as we'll see shortly. If neither condition is set, EKoala3 returns SERVERCALL_ISHANDLED, allowing the call to go through as usual. OLE's default message filter, remember, always returns SERVERCALL_ISHANDLED.

EKoala3 creates an instance of its message filter inside CApp::Init (EKOALA3.CPP) and registers the instance with CoRegisterMessageFilter. If for some reason it fails to instantiate the filter, EKoala3 skips the registration entirely:


m_pMsgFilter=new CMessageFilter(this);

if (NULL!=m_pMsgFilter)
{
m_pMsgFilter->AddRef();

if (FAILED(CoRegisterMessageFilter(m_pMsgFilter, NULL)))
ReleaseInterface(m_pMsgFilter);
}

The CMessageFilter constructor sets the message filter's reference count to 0 initially. The explicit call to AddRef here gives EKoala3 control of the object's lifetime. If CoRegisterMessageFilter works, it will also call AddRef on the object. If it fails, however, the ReleaseInterface macro (INC\INOLE.H) will remove EKoala3's reference count and delete the object, in which case we just do without the message filter. All of this is also reversed inside CApp::~CApp, calling CoRegisterMessageFilter again to remove the filter (which calls the object's Release), followed by ReleaseInterface to destroy the object:


if (NULL!=m_pMsgFilter)
{
CoRegisterMessageFilter(NULL, NULL);
ReleaseInterface(m_pMsgFilter);
}

While the filter remains registered, you can play around with external calls, blocking and delaying them. Because EKoala3 is a multiuse server, you can run multiple instances of ObjectUser2 against it and watch calls to different objects coming in through HandleInComingCall. With this sample, you can make a few simple modifications to allow, reject, or delay specific calls to specific objects. By blocking one object's calls and not another's, you can watch one client make successful calls while another has to wait and, after a few seconds, display the busy dialog box. Let's now take a look at the OLE UI Library, where this dialog comes from.