Click here to Skip to main content
15,878,959 members
Articles / Desktop Programming / ATL
Article

How to Use IMessageFilter: the complete edition

Rate me:
Please Sign up or sign in to vote.
4.00/5 (8 votes)
19 Feb 20066 min read 51K   536   23   8
This article shows you exactly how to create a COM object that uses IMessageFilter - both in client and server sides.

Introduction

In my previous article on IMessageFilter , I described how to use this interface in your application. I implemented an IMessageFilter interface (class IMFImpl), and my COM object was inherited from this IMFImpl. Later on I realized that this solution was not good enough, and that I needed to use IMessageFilter as an independent object. The reason for that (which is not relevant here) was the destruction of my object. Generally, after my object was killed, COM held the pointer to my message filter, and tried to kill it, causing a crash.

So, how to use IMessageFilter as a self-dependent object? Well, I couldn't find any example on the net, and so I decided to create my own solution. I have tried to explain this here so that someone might find it helpful.

Understanding the code: server side

IDL

In your COM object's IDL file, add the new interface:

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

This self-dependent message filter knows how to register and unregister itself. The registerMessageFilter and unregisterMessageFilter methods are good for registering and un-registering the message filter. The block and unblock are good for cases of reentrancy: if we encounter this case, we can decide whether to block the call, or to let it in. (The object has an internal flag that is set in these methods). Then, declare the coclass that implements this interface (MyMessageFilter):

coclass MyMessageFilter
{
   [default] interface IMyMessageFilter;
}

H / CPP files

Create H and CPP files, like in the example. Technically, we create a new STA object that knows how to register and unregister itself. In addition, it supports two methods, block and unblock. By calling these methods, the client tells the message filter what to do with the problematic calls (those that get into the object while it is handling a previous call - re-entrancy!).

Using the new message filter COM object

Your COM object, that needs the solution of this message filter, can use this message filter COM object in several ways. I chose to hold it as a member. So, my main COM object holds a smart pointer to the message filter COM object as a member:

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

Then, in the ctor, initialize this pointer:

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(); 
}

Now unregister, in the dtor:

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

Understanding the code: client side

The main() function creates the server. This way it creates the COM server, which is an STA object, into the main STA. A message filter kicks in only during the cross-apartment COM calls. If you make a COM call to the server within the same apartment - the call will not go through the message filter. Hence, in the tester, you can see that the main thread creates the server. In addition, it creates two STA worker-threads, and these workers make the calls. This way, I generate cross-apartment COM calls, and the message filter is on the air.

The main thread that created the server object has to marshal the interface pointer to the workers. That is done using the CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream (short names...).

If you look at the worker-threads, you will see that each one of them registers its own message filter as well. The reason for that is each STA thread must register its own filter. In my tester, the worker-threads have to register their message filter, and only then we will see both the client and server side of the filtering (see explanation below). This message filter can be the same as that of the server's, or they might have their own. In my example, this message filter is different from that of the server's. The idea is that a client can have many servers, and each one of them might have its own message filter. Which one should the client use? And another reason: How can the server guess what the client wants to do in case of a rejected call? That's why the client has its own message filter. This message filter is of type class CClientMessageFilter.

"Both client and server sides of filtering": As you can read in the literature, when a message filter's IMessageFilter::HandleInComingCall() returns SERVERCALL_RETRYLATER or SERVERCALL_REJECTED, COM calls the client's IMessageFIlter::RetryRejectedCall(). If you want your client to have a special behavior in these cases, it has to register a message filter itself.

I use a message pump on the server thread (the one that created the component in the first place). Message pump is a must here in this case, and you cannot use an event, for example, WaitForSingleObject(), because the main thread is an STA thread, and an STA thread must not block. When it blocks, it cannot accept the incoming COM calls. That is why you must use a message pump and not a "wait" function.

Important things to know

  1. The server doesn't have to implement an IMessageFilter, it only needs to register an object that does so. The former has to be running in the same STA apartment where the latter is registered. There can only be one message filter registered per STA thread, but a single thread can house any number of COM objects.
  2. We suppose to have here three COM objects:
    1. The server.
    2. The one that implements IMessageFilter and is registered in the server thread.
    3. The one that implements IMessageFilter and is registered in the calling (client) thread.

    The latter two may be two instances of the same COM object, or may be two distinct independent implementations, as you see fit. The last one, the client's message filter, need not be creatable, and does not need any registration. See in the example code what is sufficient (class CClientMessageFilter).

  3. The easiest way to reproduce reentrancy would probably be to create two worker threads and have each of them perform one call (and have the called object put up the message box). One of these calls will nest inside the other. This is what my console-tester does.
  4. The implementation of Foo() in the server (the message box), causes a reentrancy of type CALLTYPE_TOPLEVEL. This means that actually everything is OK and the reentrant call is ready to be handled. I wanted to reproduce the problematic case, in which the message filter returns SERVERCALL_RETRYLATER or SERVERCALL_REJECTED. This happens in call type of cases CALLTYPE_TOPLEVEL_CALLPENDING or CALLTYPE_NESTED. In general, CALLTYPE_TOPLEVEL_CALLPENDING only happens when you receive an incoming cross-apartment COM call while an outgoing cross-apartment COM call is in progress. That is, when the server accepting the calls is also making other calls. It does not arrive in all cases where reentrancy is possible. In your application, do not return SERVERCALL_RETRYLATER in case of CALLTYPE_TOPLEVEL, but in this tester, I wanted to demonstrate how it works in case of SERVERCALL_RETRYLATER, so for every X calls I returned this value.
  5. If you forget to write in your RGS file the ThreadingModel=Apartment clause, the component is treated as a legacy single-threaded component, which means that all its instances must be created on a single thread - so called main STA thread. When the worker thread tries to create it, the interface needs to be marshaled. But IMessageFilter is unmarshallable, because IMessageFilter hooks into marshalling process. Imagine: you are making a marshaled cross-apartment call. For one reason or the other a message filter needs to be called. But it lives in a different thread, so the call to the message filter itself needs to be marshaled. But this marshalling may result in the necessity to call a message filter, which lives in a different apartment. IMessageFilter is unmarshallable because its methods don't return HRESULT.

Conclusion

I owe my knowledge about message filters to Igor Tandetnik. Without his help, this article would not have been written.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Israel Israel
working for Intel

My Linkedin Profile

Visit my photography gallery

Comments and Discussions

 
QuestionWhat is for CServer::ProcessIncomingCall? Pin
ehaerim18-Jan-14 15:38
ehaerim18-Jan-14 15:38 
Questionnot clear about when to use block/unblock Pin
ehaerim18-Jan-14 12:41
ehaerim18-Jan-14 12:41 
QuestionSample source isn't working Pin
reinraus10-Aug-11 6:22
reinraus10-Aug-11 6:22 
QuestionCompile errors in VS2005 Pin
orhor15-Oct-06 23:17
orhor15-Oct-06 23:17 
GeneralProblem on perf error Pin
jacksmith1239-Jul-06 3:21
jacksmith1239-Jul-06 3:21 
Generalcomments Pin
Ohad Redlich15-Feb-06 4:29
Ohad Redlich15-Feb-06 4:29 
GeneralRe: comments Pin
krssagar22-Feb-06 4:03
krssagar22-Feb-06 4:03 
GeneralRe: comments Pin
Ohad Redlich22-Feb-06 4:56
Ohad Redlich22-Feb-06 4:56 
of course it does. please have a look in the sample program...
(and please vote for this article Smile | :) )

when you say - "When Multiple threads accessing the same Interface, somehow the messages are dispatched between the calls" - and u have mentioned that u have STA COM object, so this is EXACTLY the re-entrancy problem! so of course, you can use this 'technique' as a solution.

Ohad

Visit my photography exhibition


-- modified at 11:14 Wednesday 22nd February, 2006

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.