Can't trap events while multithreaded

Posted on 2005-05-14
Last Modified: 2013-11-20

I am writing an interface class between my application and a third party networking library/object (echelon LNS object server), accessed as an ATL component using the #import command (excuse me if I use a little wrong terminology, I am somewhat new to the COM world).  To trap events from the object, I have to create my own event sink class after instantiating the LNS object and advising the event sink with the new object, as excerpted in [1].  The implementation of the event sink is shown in [2], which shows the prototype and handler for the event I'm trying to capture, OnServicePin.

This works like a charm, I can trap the 'OnSystemServicePin' event, UNLESS I run the Initialize method on its own thread, by spawning a new thread [Excerpt 3] and then the thread, once started, runs the NetworkInterface initialize method [Excerpt 1] (which includes the CreateInstance, EventSink construction, and DispEventAdvise).  I need the initialize (and other functions) to be called from a separate thread because it takes a while and I'd like to take care of other things in the main thread.  But when I call these from the spawned thread, I can't get the event handlers in CLNSEventSink [Excerpt 2] to fire.  (These events originate from the third-party (echelon) LNS object, which senses certain network conditions.)

What is it about doing things in a spawned thread that screws up my ability to catch those events?  I have a feeling that at the heart of this there is a fundamental COM principle that I am missing.

Sorry if this is a convoluted explanation, not sure which details are important.  Any thoughts will be appreciated, and I'll provide clarification as necessary.

Thanks very much.

[Excerpt 1]

lca::_DLcaObjectServerPtr m_pObjSvr;
CLNSEventSink* m_pLNSSink;
(NetworkInterface.cpp, Initialize method)
m_pObjSvr.CreateInstance (_T("LonWorksObjectServer.1"))
m_pLNSSink = new CLNSEventSink();      
HRESULT h = m_pLNSSink->DispEventAdvise(IUnknownPtr(m_pObjSvr));

[Excerpt 2]

(CLNSEventSink implementation)
static _ATL_FUNC_INFO OnSystemServicePinInfo = {CC_STDCALL, VT_EMPTY, 9, {VT_I4, VT_I4, VT_I2, VT_BSTR, VT_BSTR, VT_BSTR, VT_I4, VT_I2, VT_I4}};

class CLNSEventSink : public IDispEventSimpleImpl<0, CLNSEventSink, &lca::DIID__DLcaObjectServerEvents>
      // constructor
      CLNSEventSink()      {}
      virtual ~CLNSEventSink(){      }

      void __stdcall OnSystemServicePin ( long NetworkHandle, long SystemHandle, short EventTag, LPCTSTR NeuronId, LPCTSTR ProgramId, LPCTSTR Location, long ChannelHandle, short AuxClassId, long ObjectHandle )
            MessageBox(NULL, "received service pin", "debug", 0);
            SINK_ENTRY_INFO(0, lca::DIID__DLcaObjectServerEvents, 0x2, OnSystemServicePin, &OnSystemServicePinInfo)

[Excerpt 3]

(NetworkInterface constructor)

::AfxBeginThread(NetworkThread, m_pThreadData, THREAD_PRIORITY_NORMAL, 0, 0, NULL);


Question by:riceman0

    Author Comment

    Right now, you'll notice I am using a worker thread.  My feeling is that this shouldn't require a user interface thread, because I am  not using a CWnd or any windows stuff while I am NOT doing it threaded (I use a manual event sink and DispEventAdvise), and receive the messages fine (the "works like a charm" part above), so why should I have to when I put it on a thread?  It seems like it should work exactly the same way, having just moved everything as-is to a different thread...

    Wish I could raise the points more.
    LVL 9

    Expert Comment

    Are you calling CoInitializeEx in your worker thread?


    Author Comment


    I am calling CoInitialize in the worker thread.  I see that CoInitializeEx allows you to specify a concurrency model, which I admittedly don't understand very well.  According to MSDN, by using CoInitialize I am using the STA model.  Were you just making sure I was starting COM in the thread, or is there a difference that could be relevant here?

    Note that everything works fine when I start it on the thread (can activate the object's functions), except for the fact that I can't get my event sink (which is supposed to trap events back from the object) to fire.
    LVL 9

    Expert Comment

    For incoming COM calls, STA relies on messages being pumped by the corresponding thread. Presumably, your worker thread doesn't have a message loop. Try using CoInitializeEx and pass in COINIT_MULTITHREADED.


    Author Comment


    That would be beautiful if it solves it, but having a problem trying it: when I replace it with CoInitializeEx, I get an undeclared identfier.  MSDN says it is declared in objbase.h, which i then included.  Still get the undeclared identifier error.  When I look at objbase.h, I see the code below.  So I have to have >= 0x0400 to use it.  Isn't windows XP >= 0x0400?  Any ideas?

    I'll continue trying your solution, let me know if you have any ideas though.


    #if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM
    /* #!perl PoundIf("CoInitializeEx", "(_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM)");
    WINOLEAPI  CoInitializeEx(IN LPVOID pvReserved, IN DWORD dwCoInit);

    /* #!perl PoundIf("CoGetCallerTID", "(_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM)");
    #endif // DCOM
    LVL 9

    Expert Comment

    You have to define the _WIN32_WINNT symbol as being 0x0400 or greater. This is the NT kernel version on which you plan to run your software (not the version of Windows under which you are performing the build).

    You can either put   #define _WIN32_WINNT 0x0400   at the top of your stdafx.h file or, even better, place   _WIN32_WINNT=0x0400   in the Preprocessor definitions edit-box under the project settings.


    Author Comment


    Okay, got by that with a

    #define _WIN32_WINNT = 0x0500;

    but -- %#@$! -- that didn't solve it.  Still, the same code traps the event when I call it from the local thread, but misses it somehow when I call it from the spawned worker thread, even with CoInitializeEx.  That would've been nice, though.

    I think I will start (painfully) reimplementing it as a user interface thread, mostly because I don't know what else to do.  Not even sure this will solve it.  Any other ideas?  Anyone?  Bueller?

    Author Comment

    Missed your last post, thanks.  Any other ideas on the events?

    Author Comment


    rcarlan: I actually think that I might not've been using CoInitializeEx before when I was testing your solution.  I tried it again, and I'm getting weird results -- the third-party object that I am creating on the thread behaves differently when I use a multithreaded apartment model.  Some of the calls to the object that are supposed to return pointers fail when I try to cast them to expected types.  Then it works again when I switch back to CoInitialize.  This is totally unexpected, not sure if it casts any light on the problem...

    Author Comment


    Okay, fixed it by changing it from a worker thread to a UI thread.  What a nightmare.  

    But before I ask for a refund, maybe I'll keep this open for someone who can educate me a little on COM, 500 points worth.  What I'm hoping for is something -- your thoughts, a discussion, specific references or articles -- tuned to where I'm at, what I know and don't know (probably have given a good idea of that in this question), something to bring me to the next level.  Right now I can eventually get things to work, but often don't fully understand what I've done (like now).  Some specific questions, answers in quality or quantity could work:

    1) what do the different apartment models really mean, logically and physically, and when would you use which
    2) what is the physical and logical difference between worker threads and UI threads
    3) what is an event, really?  what is a connection point?
    4) how are COM objects stored in the registry?

    Thanks.  Just thought I'd throw this out there.  Documentation is what it is, but there is nothing like a little education that is well-targeted to one's skill level...
    LVL 9

    Accepted Solution

    Sorry I didn't have time to look into your problem further. Been very busy at work today.
    I'm happy, though, you found a solution that works.

    To answer your questions:

    1) In the simplest scenario COM provides a means of communication between two modules (client and server) developed independently, potentially using different programming languages. COM imposes no restrictions on how the two modules are implemented. They can reside in the same process, they can run as separate processes, indeed they can exist on different machines and communicate over the network. And, of course, there can be many different clients talking to the same server.

    This flexibility raises certain synchronisation issues – in particular, regarding synchronizing access to shared resources (server and server data). This is where apartments come in.

    The apartment model of a COM component controls concurrent access to the server. There are two apartment models: STA (single threaded apartment) and MTA (multi-threaded apartment). There was talk of a third one – RTA (rental threaded apartment), but it never eventuated.

    Both the client and the server need to initialize the COM library before they make any COM calls, and during initialization each specifies its apartment model affinity. In COM terms, this is referred to as ‘entering an apartment’. It may seem strange at first, but in fact each thread in a process needs to enter the apartment separately (this only applies to threads that perform COM calls).

    Why or how could a thread be STA or MTA – since it’s just one thread? Well, the apartment model controls access to objects created by the thread. Once the thread has created an object, the object becomes an entity that is theoretically independent of the thread that created it. Indeed, the object is created by a thread in the server, but can be used by multiple different threads running in different client processes. Thus, the object is placed in an apartment, and the apartment model controls what threads can enter and use the object. Once an object has been placed in apartment, it cannot be moved to a different one, nor can its apartment model be changed.

    With STA, only the thread that has created the object can enter the apartment. So, when a call comes from the outside (i.e. a client calls a method on the object), the call is transferred to the server thread that created the object, and it is the server thread that actually executes the call. How is this achieved? Through Windows messages. Thus, STA servers need to have a message loop and need to pump messages in order to service calls. Stop the message loop and the server appears frozen (unresponsive) to its clients.

    In the case of MTA, you can have multiple simultaneous threads running through the apartment. As opposed to STA, in the case of MTA, COM does not serialize calls to the server. This means the server has to take care of protecting its data against concurrent use. This is usually done with critical sections. Needless to say, it is only threads of the server process that can execute methods on a COM object. So, when a call comes from an external client, it is marshalled to the COM server process and executed by one if its threads. There’s usually a pool of threads running in a COM server waiting for requests from outside. I think by default, you get four threads in a COM server, but you can obviously add or remove threads.

    2) A U/I thread has a message loop, a worker thread doesn’t. Both worker threads and U/I threads can be viewed as a procedure (function). When the function returns, the thread ends. It’s just that a U/I thread runs a message loop in this function and doesn’t return until it receives a WM_QUIT message (until then it loops peeking, getting and processing messages). MFC provides convenient ways of creating worker threads and U/I threads, and makes a clear distinction between them. However, for the O/S, the two are no different. Indeed, you can implement your own message loop in an MFC worker thread and thus make it a ‘U/I thread’. However, if you want to integrate with the MFC message maps and associated functionality, you will have to create a ‘proper’ MFC U/I thread.

    3) A connection point is COM’s implementation of the observer pattern. In its simplest form, the observer pattern involves two entities: a subject and an observer. The subject performs some actions and, as a result, fires events (or notifications) to the observer.

    If you are a seasoned MFC developer, you would be familiar with the Document-View framework in MFC. It is another classic example of the observer pattern. The document is the subject and the view is the observer. The document notifies the view of changes by calling UpdateAllViews which ends up calling the OnUpdate method in the view. Usually, you have many observers to one subject (which is what you have in the doc-view framework in MFC – each document can have many views).

    In COM, connection points are used by the server to notify its clients of events occurring in the server. Each server has its own collection of events. These events are defined as methods in an interface. The corresponding interface is published by the server in its IDL file (quite often, especially for automation clients, the interface is published in the associated TLB). Clients interested in being notified of events, have to implement this interface and register their implementation with the server (i.e. connect it to the server).  When the server fires an event, the roles of client and server are reversed: the client becomes the server, and the server becomes the client – in the sense that it will be the original server that will call a method on the COM object implemented by the original client (this COM object is usually called an ‘events sink’).

    4) COM objects are not really ‘stored’ in the Registry – only their identity and some additional information required for creating running instances is stored in the Registry.

    Each COM server implements one or more COM objects. A COM object is actually a class in C++ lingo. In other words, each COM object can have one or more instances. The COM object is defined by the interface(s) it exposes (or implements).

    Each COM object has to have a unique and unambiguous identity. In C++ (and all other OO languages I know), the identity is just a name. This is usually sufficient because within a project (domain) the likelihood of name collisions is small and the risk can be managed – quite often by using namespaces. In COM, which is open to the whole world, it is much more difficult to prevent name collisions. Thus, COM objects are identified through very large numbers – so called GUIDs or UUIDs (globally or universally unique identifiers). Each COM object is given a GUID by its implementor. Not only that, but each interface implemented by a COM object also has its own GUID. GUIDs are also used to identify libraries (collections of COM objects) as well as applications (COM servers). This information is stored in the Registry.

    COM object identity is stored under HKLM\Software\Classes\CLSID. Each COM object registered on the local system has an entry under this Registry key. The entry is a registry key having as a name the textual representation of the COM object’s GUID. Under this key there is additional information required by the system to instantiate the COM object when requested by a client. HKLM\Software\Classes\Interface stores information about all interfaces implemented by registered COM objects, HKLM\Software\Classes\TypeLib stores information about registered COM libraries and HKLM\Software\Classes\AppID stores information about registered COM servers.

    In addition to numbers, COM objects can also be identified by a name (just like a C++ class). These names are used by COM automation (e.g. by scripting languages, VB, etc). They can also be used by C++ programs, though in case of C++, you don’t really need a name – you can use the GUID. If an object is given a name (which is not really mandatory, but most COM developers do it in order to support a wide variety of clients), the name is also registered under HKLM\Software\Classes.

    COM is a very complex technology, and there is only so much you can put in a post on the Web. There are many books out there describing the various facets of COM. If you are really keen to get a better understanding of COM, I recommend Essential COM by Don Box (ISBN: 0-201-63446-5). It is acknowledged as being the best introductory book to COM, and, in my personal opinion, it’s one of the best programming books I’ve ever read. In addition, if you want to develop COM servers, you may want to also get a copy of ATL Internals by Brent Rector and Chris Sells (ISBN: 0-201-69589-8).

    I hope the above made sense to you.



    Author Comment

    Yeah, I'd say you earned the points.  Thanks a bunch.

    Write Comment

    Please enter a first name

    Please enter a last name

    We will never share this with anyone.

    Featured Post

    Threat Intelligence Starter Resources

    Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

    Suggested Solutions

    Title # Comments Views Activity
    Adapt this command to show who installed 29 99
    sum67 challenge 35 76
    sum28 challenge 31 82
    NotAlone Challenge 20 65
    Introduction: Hints for the grid button.  Nested classes, templated collections.  Squash that darned bug! Continuing from the sixth article about sudoku.   Open the project in visual studio. First we will finish with the SUD_SETVALUE messa…
    Introduction: Dialogs (2) modeless dialog and a worker thread.  Handling data shared between threads.  Recursive functions. Continuing from the tenth article about sudoku.   Last article we worked with a modal dialog to help maintain informat…
    This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
    This video discusses moving either the default database or any database to a new volume.

    779 members asked questions and received personalized solutions in the past 7 days.

    Join the community of 500,000 technology professionals and ask your questions.

    Join & Ask a Question

    Need Help in Real-Time?

    Connect with top rated Experts

    14 Experts available now in Live!

    Get 1:1 Help Now