Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C++
Article

Understanding COM Event Handling

Rate me:
Please Sign up or sign in to vote.
4.91/5 (95 votes)
9 Dec 2004CPOL19 min read 573.6K   7.4K   219   141
Learn the fundamental principles of COM Event Handling via a C++ template class that allows for generic handling of dispinterface COM events.

Change of Article Title

Please note that I have changed the title of this article from "TEventHandler - A C++ COM Event Handler For IDispatch-Based Events" to the current title "Understanding COM Event Handling." The latter, I believe, is a better title which gives a more accurate picture of the intended theme of this article, i.e. to expound carefully the internal mechanisms behind COM event handling.

Introduction

If you have ever done any development work involving the use of COM objects, chances are you would have encountered the need for COM object event handling. Visual Basic users will know how simple it is to connect with the event interface(s) of the COM (or ActiveX) objects. The VB IDE lays out the event handling function codes nicely for the user. All the user has to do is to fill in the details of the event handling functions.

For Visual C++ users, this is not always so straight forward. If your COM object happens to be an ActiveX control and you are using MFC, then yes, the Visual C++ IDE provides Wizards that can help you generate event handler function stubs. All the necessary codes (e.g. inserting the event sink map and event entry macros) are done automatically for you.

But what if your COM object is not an ActiveX control? What if you are using straight COM objects which also fire events and you need to handle those events?

If you are an MFC user, you may want to tinkle with the various MFC macros to see if you can fit in an event handler function into your code either manually or via the Wizard. I personally believe this is possible. But you need to be armed with an intimate knowledge of MFC and its generated macros.

If you do not use MFC, you may want to experiment with ATL codes (e.g. IDispEventImpl, BEGIN_SINK_MAP, SINK_ENTRY_EX, etc.) to perform event handling. The ATL macro codes are certainly not simple but they are well-documented in MSDN and they do provide standard handling mechanisms.

In this article, I will go back to basics and seek to explain the fundamental principles of how event handling is done in COM. I will also provide a C+ class which serves as a basic and simple (at least in terms of code overhead) facilitator for COM Object Event Handling.

I do this via a special custom-developed template class named TEventHandler which I have used in many projects. This class uses COM first principles and primitives and avoids the use of complicated macros. The sections following expound this class in detail. I assume that the reader is suitably conversant with C++, ATL and the concepts of template classes. However, before we start discussing the TEventHandler class, let us explore the fundamental principles of Event Handling in COM.

Event Handling in COM

Incoming Interfaces

When we develop our usual COM objects, we provide implementations for interfaces (defined in an IDL file) which we write ourselves or have been supplied to us. Such implementations facilitate what are known as "incoming" interfaces. By "incoming," we imply that the object "listens" to its client. That is, the client calls methods of the interface and, in this way, "talks" to the object.

Referring to the diagram below, we can say that ISomeInterface is an "incoming" interface provided by the COM object on the right.

Image 1

Outgoing Interfaces

As Kraig Brockschmidt puts it so well in his book "Inside OLE," many COM objects themselves have useful things to say to their clients. And clients may want to listen to COM objects too. If such a two-way dialog is desired, something known as an "outgoing" interface is required. The term "outgoing" is used in the context of the COM object. It is outgoing in the perspective of the COM object. Imagine a situation in which the role of "talker" and "listener" is reversed as shown in the diagram below:

Image 2

Referring to the diagram above, we can say that ITalkBackInterface is an "outgoing" interface supported by the COM object on the right. The COM object invokes the methods of ITalkBackInterface and it is the Client that implements the ITalkBackInterface methods. A COM object that supports one or more outgoing interfaces is known as a Connectable Object or a Source. A connectable object can support as many outgoing interfaces as it likes. Each method of the outgoing interface represents a single event or request. All COM objects, regardless of whether they are non-visual COM objects or ActiveX controls (generated manually or via MFC or ATL), use the same mechanism (connectability) for firing events to their clients.

Events and Requests

Events are used to tell a client that something of interest has occurred in the object - a property has changed or the user has clicked a button. Events are particularly important for COM controls. Events are fired by COM objects and no response from the client is expected. In other words, they are simple notifications. Requests, on the other hand, is how a COM object asks the client a question and expects a response in return. Events and requests are similar to Windows messages, some of which inform a window of an event (e.g. WM_MOVE) and some will ask for information from the window (e.g. WM_QUERYENDSESSION).

Sinks

In both cases, the client of the COM object must listen to what the object has to say and then use that information appropriately. It is the client, therefore, that implements the outgoing interfaces which are also known as sinks (I really dislike this name but it has become common and ubiquitous in the world of COM and .NET). From a sink's perspective, this outgoing interface is actually incoming. The sink listens to the COM object through it. In this context, the connectable COM object plays the role of a client.

How Things are Tied Up Together

Let us take a helicopter view of the entire communications situation. There are three participants:

  • The COM object itself
  • The Client of the COM object
  • The Sink

The Client communicates with the COM object as usual via the object's incoming interface(s). In order for the COM object to communicate back to the client in the other direction, the COM object must somehow obtain a pointer to an outgoing interface implemented somewhere in the client. Through this pointer, the COM object will send events and requests to the client. This somewhere is the Sink. Let us illustrate the above with a simple diagram:

Image 3

Note that the Sink is an object by itself. The Sink provides the implementation for one or more outgoing interfaces. It is also usually strongly tied to the other parts of the client's code because the whole idea of implementing a sink is for the client code to be able to react to an event and/or to respond to some request from the COM object.

How Does a COM Object Connect to a Sink?

But how does a COM object connect to a Sink in the first place? This is where the notion of Connection Points and Connection Point Containers come in. We will explore this in detail in the next section.

Connection Points and Connection Point Containers

For each outgoing interface that a COM object supports (note the use of the word "support;" the COM object itself does not implement this interface, but rather, invokes it), the COM object exposes a small object called a connection point. This connection point object implements the IConnectionPoint interface.

It is through this IConnectionPoint interface that the client passes its Sink's outgoing interface implementation to the COM object. Reference counts of these IConnectionPoint objects are kept by both the client and the COM object itself to ensure the lifespan of the two-way communications.

The method to call (from the client side) to establish event communication with the COM object is the IConnectionPoint::Advise() method. The converse of Advise() is IConnectionPoint::Unadvise() which terminates a connection. Please refer to MSDN documentation for more details of these methods.

Hence, via IConnectionPoint interface method calls, a client can start listening to a set of events from the COM object. Note also that because a COM object maintains a separate IConnectionPoint interface for every outgoing interface it supports, a client must be able to use the correct connection point object for every sink it implements.

How then, does the Client choose the appropriate connection point for a sink? In comes Connection Point Containers. An object which is connectable must also be a Connection Point Container. That is, it must implement the IConnectionPointContainer interface. Through this interface, a Client requests for the appropriate Connection Point object of an outgoing interface.

When a client wants to connect a Sink to a Connection Point, it asks the Connection Point Container for the Connection Point object for the outgoing interface implemented by that Sink. When it receives the appropriate connection point object, the Client passes the Sink's interface pointer to that connection point. The IConnectionPointContainer interface pointer itself can be obtained easily via QueryInterface() on the COM object itself. Nothing speaks better than an example code which is listed below:

C++
void Sink::SetupConnectionPoint(ISomeInterface* pISomeInterface)
{
  IConnectionPointContainer* pIConnectionPointContainerTemp = NULL;
  IUnknown*     pIUnknown = NULL;
  /*QI this object itself for its IUnknown pointer which will be used */
  /*later to connect to the Connection Point of the ISomeInterface object.*/
  this -> QueryInterface(IID_IUnknown, (void**)&pIUnknown);
  if (pIUnknown)
  {
    /* QI pISomeInterface for its connection point.*/
    pISomeInterface -> QueryInterface (IID_IConnectionPointContainer, 
                       (void**)&pIConnectionPointContainerTemp);
 
    if (pIConnectionPointContainerTemp)
    {
     pIConnectionPointContainerTemp -> 
           FindConnectionPoint(__uuidof(ISomeEventInterface), 
           &m_pIConnectionPoint);
     pIConnectionPointContainerTemp -> Release();
     pIConnectionPointContainerTemp = NULL;
    }
 
    if (m_pIConnectionPoint)
    {
     m_pIConnectionPoint -> Advise(pIUnknown, &m_dwEventCookie);
    }
 
    pIUnknown -> Release();
    pIUnknown = NULL;
  }
}

The sample function above describes the SetupConnectionPoint() method of a Sink class. The Sink class implements the methods of the outgoing interface ISomeEventInterface. The SetupConnectionPoint() method takes a parameter which is pointer to an interface named ISomeInterface. The COM object behind ISomeInterface is assumed to be a Connection Point Container. The following is an outline of the function's logic:

  1. We first QueryInterface() the Sink object itself for its IUnknown interface pointer. This IUnknown pointer will be used later in the call to IConnectionPoint::Advise().
  2. Having successfully obtained the IUnknown pointer, we next QueryInterface() the object behind pISomeInterface for its IConnectionPointContainer interface.
  3. Having successfully obtained the IConnectionPointContainer interface pointer, we use it to find the appropriate Connection Point object for the outgoing interface ISomeEventInterface.
  4. If we are able to obtain this Connection Point object (it will be represented by m_pIConnectionPoint), we will proceed to call its Advise() method.
  5. From here onwards, whenever the COM object behind pISomeInterface fires an event to the sink (by calling one of the methods of ISomeEventInterface), the corresponding method implementation in the Sink object will be invoked.

I certainly hope that the above introductory sections on Event Handling in COM, Connection Points and Connection Point Containers will have served to provide the reader with a clear understanding of the basics of event handling. It is worth re-mentioning that the above principles are used whatever the type of COM object is involved (e.g. straight COM object, ActiveX controls, etc). With the basics explained thoroughly, we shall proceed to expound on the sample source codes, especially the TEventHandler template class.

The Sample Source Code

I will attempt to explain TEventHandler by running through some example codes. Please refer to the source files which are contained in the TEventHandler_src.zip file. In the set of sample codes, I have provided two sets of projects:

  • EventFiringObject
  • TestClient

EventFiringObject

The EventFiringObject project contains the code for a simple COM object which implements an interface named IEventFiringObject. This COM object is also a Connection Point Container which recognizes the _IEventFiringObjectEvents connection point. The relevant IDL constructs for this COM object is listed below for discussion purposes:

MIDL
[
 object,
 uuid(8E396CC0-A266-481E-B6B4-0CB564DAA3BC),
 dual,
 helpstring("IEventFiringObject Interface"),
 pointer_default(unique)
]
interface IEventFiringObject : IDispatch
{
 [id(1), helpstring("method TestFunction")]
        HRESULT TestFunction([in] long lValue);
};
[
 uuid(32F2B52C-1C07-43BC-879B-04C70A7FA148),
 helpstring("_IEventFiringObjectEvents Interface")
]
dispinterface _IEventFiringObjectEvents
{
 properties:
 methods:
 [id(1), helpstring("method Event1")] HRESULT Event1([in] long lValue);
};
[
 uuid(A17BC235-A924-4FFE-8D96-22068CEA9959),
 helpstring("EventFiringObject Class")
]
coclass EventFiringObject
{
 [default] interface IEventFiringObject;
 [default, source] dispinterface _IEventFiringObjectEvents;
};

In the EventFiringObject project, we implement a C++ ATL class named CEventFiringObject which implements the specifications of coclass EventFiringObject. CEventFiringObject provides a simple implementation of the TestFunction() method. It simply fires Event1 which is specified in _IEventFiringObjectEvents.

C++
STDMETHODIMP CEventFiringObject::TestFunction(long lValue)
{
 /* TODO: Add your implementation code here */
 Fire_Event1(lValue);
 return S_OK;
}

TestClient

TestClient is a simple test application: an MFC dialog-based application which instantiates the EventFiringObject COM object. It also attempts to handle the Event1 event fired from EventFiringObject. I will walk through the TestClient code more thoroughly to explain the process of event handling. The client code centers around the CTestClientDlg class which is derived from CDialog. In TestClientDlg.h, notice that we declare an instance of a smart pointer object which will be tied to the COM object which implements IEventFiringObject:

C++
/* ***** Declare an instance of a IEventFiringObject smart pointer. ***** */
 IEventFiringObjectPtr   m_spIEventFiringObject;

Then, in the CTestClientDlg::OnInitDialog() function, we instantiate m_spIEventFiringObject:

C++
/* ***** Create an instance of an object 
          which implements IEventFiringObject. ***** */
 m_spIEventFiringObject.CreateInstance(__uuidof(EventFiringObject));

We also create a button in our simple dialog box labeled "Call Test Function." In the click handler for this button, we invoke the TestFunction() of our m_spIEventFiringObject:

C++
/* ***** Call the IEventFiringObject.TestFunction(). ***** */
 /* ***** This will cause the object which implements ***** */
 /* ***** IEventFiringObject to fire Event1. ***** */
 m_spIEventFiringObject -> TestFunction(456);

Thus far, we have dealt with mostly typical COM client code. The fun begins when we invoke TestFunction(). We know that TestFunction() will cause the m_spIEventFiringObject COM object to fire the Event1 event. This is where the real action starts.

The TEventHandler Class

General Design Goals and Example Use Case

The TEventHandler class is supplied in TEventHandler.h. It works according to the following design:

  • It serves the role of a Sink for a client.
  • It generically handles one event interface which must be dispinterface-based (i.e. derived from IDispatch).
  • After receiving an event fired from the COM object, TEventHandler will call a predefined method of its client. This is how TEventHandler clients get notified of events fired from the COM object.

The predefined method of the client must be defined using the following signature:

C++
typedef HRESULT (event_handler_class::*parent_on_invoke)
(
 TEventHandler<event_handler_class, device_interface, 
 device_event_interface>* pthis,
 DISPID dispidMember, 
 REFIID riid,
 LCID lcid, 
 WORD wFlags, 
 DISPPARAMS* pdispparams, 
 VARIANT* pvarResult,
 EXCEPINFO* pexcepinfo, 
 UINT* puArgErr
);

Notice that the predefined method's parameters match those of IDispatch::Invoke() except for the addition of a parameter (first in the list) which is a pointer to the TEventHandler instance itself. This parameter is supplied as it may be useful to client code. In the context of our example code, our usage of TEventHandler can be represented by the following diagram:

Image 4

What TEventHandler is Not Designed to Do

Why do we make an apparent rehash of the IDispatch::Invoke() method? What value-add could TEventHandler have if your callback function still has to handle all the parameters of the IDispatch::Invoke() call?

The answer is that the TEventHandler class is not primarily designed to simplify the handling of the parameters of the event methods (although this might be possible, see my comments on this later in this section).

It is designed to be a sink object. It is meant to readily make available (for a client) a sink which can be hooked up to receive the dispinterface-based event of a COM object. And then have the sink automatically call the mirror Invoke() method of the client.

Note that using C++ templates alone, it will not be possible to anticipate in advance the return values and parameter lists of event methods. This would require the work of Wizards which can read all these information from the type libraries associated with the connectable COM objects and then generating function codes which match the signatures of event methods.

TEventHandler is not a wizard. It is a C++ template (which makes it sort of a bona-fide code generator, but it can't do everything, e.g. read a type library and generate code according to information found therein...). It is also meant to be a Sink for one event interface which must be derived from IDispatch, and hence TEventHandler implements IDispatch (see the next section "Why Is TEventHandler Dispinterface-based?" for more information on the reasons behind this).

Why is TEventHandler Dispinterface-based?

In order for TEventHandler to be a Sink for ISomeEventInterface, it must be derived from ISomeEventInterface and it must implement the methods of that interface. I have chosen to make TEventHandler derive from IDispatch, thereby making it a Sink for an event interface that also derives from IDispatch.

Interfaces that derive from IDispatch are also known as dispinterfaces (for dispatch interfaces). Interfaces, in general (not just event interfaces), that are dispinterfaces are very common. They are common because of the long-time need for COM to support Visual Basic.

The IDispatch interface is the basis behind Visual Basic's achievement of something known as late binding which means the act of programmatically constructing a function call (together with the inclusion of parameters and the receipt of return values) at runtime. It is perhaps the most generic and flexible of all COM interfaces.

Take note that I'm using the term late binding in a generic way (I'm not referring to the C++ concept of vtables which is another implementation of late binding). IDispatch can be used to define a virtual interface the methods of which are called via the IDispatch::Invoke() method. This system of runtime function invocation is known as automation (formerly OLE-automation).

To distinguish one IDispatch virtual interface from another, something known as a Dispatch Interface ID is used (DIID). This is programmatically no different from a normal Interface ID (IID). Take a look at the following code fragment:

C++
pIConnectionPointContainerTemp -> FindConnectionPoint
(
  __uuidof(ISomeEventInterface), 
  &m_pIConnectionPoint
);

Here, we are asking a connection point container to return to us a connection point that supports the outgoing interface that is identified by the DIID which is equivalent to the result of __uuidof(ISomeEventInterface).

Visual Basic applications can only handle ActiveX object events through sinks which are dispinterface-based. This is natural both because of the fact that Visual Basic cannot handle non-dispinterface-based events, and also because of the need to handle events fired from any and all ActiveX objects.

The central point behind this is that while event interfaces need not be dispinterface-based (their methods can be of any signature, the only mandate is that the event interface must also derive from IUnknown), Visual Basic is not able to internally anticipate the design of these custom event interfaces and to generate Sinks for them.

Furthermore, the types of method return values and parameters must be confined to those that Visual Basic is able to understand and internally process. Hence, the only way to standardize the handling of event interfaces is to require that they be derived from IDispatch, and that the return and parameter types be from a wide but limited ranged set. This set of types is known as the automation-compatible-types.

Like Visual Basic, it is not possible for us to design the TEventHandler template class to be a Sink for custom event interfaces. Remember that we must actually implement the methods of the event interface that TEventHandler is to be a Sink for. Hence, I decided that TEventHandler should handle only dispinterface-based events. And there are plenty of COM object sources which support them.

Code Details

The TEventHandler template class is defined (in summary) as follows:

C++
template <class event_handler_class, typename device_interface, 
  typename device_event_interface>
class TEventHandler : IDispatch
{
  ...
  ...
  ...
}

Note that TEventHandler is derived from IDispatch. However, note, it need not implement all of the methods of IDispatch. Only the basic AddRef(), Release(), QueryInterface() and the Invoke() methods are required at minimum. TEventHandler takes in three template parameters which are:

  1. event_handler_class
  2. device_interface
  3. device_event_interface

The event_handler_class parameter indicates to TEventHandler the name of the class which will contain the predefined method to be invoked when the COM object fires an event of the outgoing interface. In out example use case, this parameter will be the CTestClientDlg class. The CTestClientDlg class will contain the invocation function:

C++
HRESULT CTestClientDlg::OnEventFiringObjectInvoke
(
  IEventFiringObjectEventHandler* pEventHandler,
  DISPID dispidMember, 
  REFIID riid,
  LCID lcid, 
  WORD wFlags, 
  DISPPARAMS* pdispparams, 
  VARIANT* pvarResult,
  EXCEPINFO* pexcepinfo, 
  UINT* puArgErr
)

It is in this function that CTestClientDlg will handle events fired from the EventFiringObject COM object. The device_interface parameter refers to the interface type of the COM object (whose event we are trying to receive). In our example use case, this will be IEventFiringObject.

The last template parameter is device_event_interface and this indicates the interface type of the outgoing interface supported by the COM object. This will be the interface that must be implemented by the Sink object. In our example use case, this will be _IEventFiringObjectEvents. And note that because _IEventFiringObjectEvents is essentially derived from IDispatch, our Sink object (which is TEventHandler) is also derived from IDispatch.

All the above template parameters are used in order that the VC++ compiler be able to generate a C++ class which has been tailored to contain methods, properties and parameter types which match those of CTestClientDlg, IEventFiringObject and _IEventFiringObjectEvents. Hence, a customized class will eventually be created for further use in the code.

Usage

Usage of the TEventHandler class is simple and straightforward. Let us walk through some example codes from TestClient:

  1. We need to define a specific class type based on TEventHandler:
    C++
    // ***** Declare an event handling class 
             using the TEventHandler template. *****
    typedef TEventHandler<CTestClientDlg, IEventFiringObject, 
      _IEventFiringObjectEvents> IEventFiringObjectEventHandler;

    Note that we are not instantiating an object here! We are merely defining a C++ class via the use of TEventHandler. We will call this new C++ class IEventFiringObjectEventHandler. IEventFiringObjectEventHandler is the customized class that we mentioned earlier. This IEventFiringObjectEventHandler is the Sink for the outgoing interface _IEventFiringObjectEvents supported by the EventFiringObject COM object.

  2. In our CTestClientDlg class, we will now define a pointer to an instance of the IEventFiringObjectEventHandler class:
    C++
    /* Declare a pointer to a TEventHandler class */
     /* which is specially tailored */
     /* to receiving events from the _IEventFiringObjectEvents */
     /* events of an IEventFiringObject object. */
     IEventFiringObjectEventHandler* m_pIEventFiringObjectEventHandler;
  3. We need to define the invoke method which will be called by the IEventFiringObjectEventHandler class object when the EventFiringObject fires an event based on the _IEventFiringObjectEvents outgoing interface. We have seen this method before and it is defined as:
    C++
    HRESULT CTestClientDlg::OnEventFiringObjectInvoke
     (
       IEventFiringObjectEventHandler* pEventHandler,
       DISPID dispidMember, 
       REFIID riid,
       LCID lcid, 
       WORD wFlags, 
       DISPPARAMS* pdispparams, 
       VARIANT* pvarResult,
       EXCEPINFO* pexcepinfo, 
       UINT* puArgErr
     );

    Please note that the reader must be familiar with the IDispatch::Invoke() method in order to be able to interpret the values contained in the various parameters of this method. See the example code in OnEventFiringObjectInvoke() itself and refer to MSDN documentation for further details.

  4. In the CTestClientDlg::OnInitDialog() function, we instantiate m_pIEventFiringObjectEventHandler:
    C++
    /* Instantiate an IEventFiringObjectEventHandler object. */
     m_pIEventFiringObjectEventHandler = new IEventFiringObjectEventHandler
     (*this, 
     m_spIEventFiringObject, 
     &CTestClientDlg::OnEventFiringObjectInvoke
     );

    Here, we instantiate with constructor parameters according to those defined for TEventHandler. The first parameter is a reference to the CTestClientDlg object. The second is the smart pointer object m_spIEventFiringObject. This will be cast to an IEventFiringObject interface pointer and so the inner pointer contained inside m_spIEventFiringObject will be supplied to the constructor.

    The last parameter is a pointer to a method of the CTEstClientDlg class which conforms to the parent_on_invoke method signature. You will note in the TEventHandler constructor that once an instance of IEventFiringObjectEventHandler is created, the SetupConnectionPoint() method is called. This method will duly perform all the required connection point protocols to establish event connectivity with the EventFiringObject COM object.

  5. When we no longer want to maintain event connection with the EventFiringObject COM object, we shutdown the connection point as in the CTestClientDlg::OnDestroy() method:
    C++
    void CTestClientDlg::OnDestroy() 
    {
     CDialog::OnDestroy();
     
     /* When the program is terminating, make sure that we instruct our */
     /* Event Handler to disconnect from the connection point of the */
     /* object which implemented the IEventFiringObject interface. */
     /* We also needs to Release() it (instead of deleting it). */
     if (m_pIEventFiringObjectEventHandler)
     {
       m_pIEventFiringObjectEventHandler -> ShutdownConnectionPoint();
       m_pIEventFiringObjectEventHandler -> Release();
       m_pIEventFiringObjectEventHandler = NULL;
     }
    }
  6. To invoke the event handling code, I have included a button in the CTestClientDlg dialog box, and the handler to this button will call the EventFiringObject's TestFunction() method which will internally fire Event1. This will lead to CTestClientDlg::OnEventFiringObjectInvoke() being called by the IEventFiringObjectEventHandler class object:
    C++
    HRESULT CTestClientDlg::OnEventFiringObjectInvoke
    (
      IEventFiringObjectEventHandler* pEventHandler,
      DISPID dispidMember, 
      REFIID riid,
      LCID lcid, 
      WORD wFlags, 
      DISPPARAMS* pdispparams, 
      VARIANT* pvarResult,
      EXCEPINFO* pexcepinfo, 
      UINT* puArgErr
    )
    {
      if (dispidMember == 0x01)  // Event1 event.
      {
       // 1st param : [in] long lValue.
       VARIANT varlValue;
       long lValue = 0;
       VariantInit(&varlValue);
       VariantClear(&varlValue);
       varlValue = (pdispparams -> rgvarg)[0];
       lValue = V_I4(&varlValue);
       TCHAR szMessage[256];
       sprintf (szMessage, "Event 1 is fired with value : %d.", lValue);
       ::MessageBox (NULL, szMessage, "Event", MB_OK);
      }
      return S_OK;
    }

In Conclusion

I certainly do hope you will find the TEventHandler class useful. C++ templates are really superb in generating generic code. There is already talk of C# providing template features. I really can't wait to get my hands on this. If you have any comments on TEventHandler, on how to improve it further, please drop me an email anytime.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer NEC
Singapore Singapore
Lim Bio Liong is a Specialist at a leading Software House in Singapore.

Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.

Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area.

Comments and Discussions

 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
Lim Bio Liong7-Oct-08 22:18
Lim Bio Liong7-Oct-08 22:18 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
ehaerim7-Oct-08 22:31
ehaerim7-Oct-08 22:31 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
Lim Bio Liong7-Oct-08 22:35
Lim Bio Liong7-Oct-08 22:35 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
ehaerim7-Oct-08 22:58
ehaerim7-Oct-08 22:58 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
Lim Bio Liong7-Oct-08 23:07
Lim Bio Liong7-Oct-08 23:07 
GeneralRe: I would like to fire events to one client only. Is this possible? Pin
ehaerim7-Oct-08 23:10
ehaerim7-Oct-08 23:10 
GeneralDo I still need pT-&gt;Lock and pT-&gt;Unlock when I use CComUnkArray&lt;1&gt;? Pin
ehaerim10-Oct-08 8:04
ehaerim10-Oct-08 8:04 
QuestionCan I CoCreateInstance the sink object independently if the Sink is an object by itself? Pin
ehaerim6-Oct-08 10:50
ehaerim6-Oct-08 10:50 
One of your paragraphs mentions:

Note that the Sink is an object by itself.


Does this mean the Sink is a legitimate COM object?

If so, once a client implementing the Sink interface is running, can I CoCreateInstance the Sink object from another external program? That is, can the client exe program act as a COM exe server implementing the Sink interface?

HaeRim
AnswerRe: Can I CoCreateInstance the sink object independently if the Sink is an object by itself? Pin
Lim Bio Liong6-Oct-08 22:53
Lim Bio Liong6-Oct-08 22:53 
GeneralWhen firing an event, why nConnections is 4, not 1 for a single client? [modified] Pin
ehaerim6-Oct-08 9:58
ehaerim6-Oct-08 9:58 
GeneralRe: When firing an event, why nConnections is 4, not 1 for a single client? Pin
Lim Bio Liong6-Oct-08 22:46
Lim Bio Liong6-Oct-08 22:46 
GeneralRe: When firing an event, why nConnections is 4, not 1 for a single client? [modified] Pin
ehaerim7-Oct-08 0:00
ehaerim7-Oct-08 0:00 
GeneralRe: When firing an event, why nConnections is 4, not 1 for a single client? [modified] Pin
ehaerim7-Oct-08 0:29
ehaerim7-Oct-08 0:29 
GeneralRe: When firing an event, why nConnections is 4, not 1 for a single client? Pin
Lim Bio Liong7-Oct-08 18:11
Lim Bio Liong7-Oct-08 18:11 
GeneralRe: When firing an event, why nConnections is 4, not 1 for a single client? Pin
Harish Pulimi5-Mar-09 2:48
Harish Pulimi5-Mar-09 2:48 
General[Message Deleted] Pin
ehaerim2-Oct-08 21:50
ehaerim2-Oct-08 21:50 
GeneralRe: Memory Leak? Pin
Lim Bio Liong2-Oct-08 22:24
Lim Bio Liong2-Oct-08 22:24 
GeneralRe: Memory Leak? Pin
ehaerim2-Oct-08 22:41
ehaerim2-Oct-08 22:41 
GeneralAbout Event Handling Pin
casensio21-Aug-08 22:44
casensio21-Aug-08 22:44 
GeneralI realized the error Pin
casensio21-Aug-08 23:05
casensio21-Aug-08 23:05 
QuestionDear Mr. Lim: Pin
Zahn Nobody28-May-08 11:32
Zahn Nobody28-May-08 11:32 
AnswerRe: Dear Mr. Lim: Pin
Lim Bio Liong6-Jun-08 7:48
Lim Bio Liong6-Jun-08 7:48 
GeneralPass object to COM method as argument Pin
Peng17-Mar-08 16:29
Peng17-Mar-08 16:29 
AnswerRe: Pass object to COM method as argument Pin
Lim Bio Liong6-Jun-08 4:24
Lim Bio Liong6-Jun-08 4:24 
GeneralRe: Pass object to COM method as argument Pin
Lim Bio Liong7-Jun-08 2:02
Lim Bio Liong7-Jun-08 2:02 

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.