How do we talk with COM the language of events and delegates

I wrote this blog entry from a real world customer issue and noticed that how little documentation exists on this topic.

I assume that you already know what is a source interface in COM and what are IConnectionPoint and IConnectionPointContainer interfaces. Please pick up any COM book to learn that (I would reccommend Essential COM by Don Box). Also, a familiarity with the events and delegates would help.

The example code in this article are picked up from a real world scenario given by a customer.


Lets assume that we have a COM server which has a source interface as defined in the following IDL.

Note: The best way to learn from this article would be to

1) create a typelibrary from the IDL given above

2) create the interop dll using tlbimp.exe

3) Use ildasm to read what is written inside the interop dll.

//Start of the idl

import "oaidl.idl";

import "ocidl.idl";






helpstring("ILegacyComObject Interface"),



interface ILegacyComObject : IDispatch{

[id(1), helpstring("method DoSomething")] HRESULT DoSomething(void);





helpstring("AtlComClient 1.0 Type Library")


library AtlComClientLib






helpstring("_ILegacyComObjectEvents Interface")


dispinterface _ILegacyComObjectEvents




[id(1) ] VARIANT_BOOL CanDoSomething();

[id(2) ] void DoneSomething();




helpstring("LegacyComObject Class")


coclass LegacyComObject


[default] interface ILegacyComObject;

[default, source] dispinterface _ILegacyComObjectEvents;



//End of the idl




In common terms we also say that the com server supports two "events" CanDoSomething and DoneSomething.

NOTE: ILegacyComObject is just some implemented interface by the com server.


When we create an inteop dll using tlbimp.exe it will contain the following classes and interfaces.


1) We need two delegates for the two events defined. Our naming convention is <interfacename>_<event name>EventHandler. Also note that since delegates are types so we will have two classes defined in the interop dll by the name:




2) We have an interface defined as _ILegacyComObjectEvents with the correct ComVisible and GUID attributes.


3) Tlbimp.exe also creates a second event interface, designated by the "_Event" suffix added to the name of the original interface. This second event interface has DoneSomething and CanDoSomething events as members. It also has add and remove methods for the event delegates. In this example, the interface is called _ILegacyComObjectEvents_Event.

 4) The coclass generated would implement ILegacyComObject and _ILegacyComObjectEvents_Event
(Note) and be named as LegacyComObjectClass. For points 3 and 4 also msdn has
some good article

5) We generate a class <source interface name>_SinkHelper which implements the source interface methods (or in other words it implements _ILegacyComObject interface). Thus we would pass an instance of this class everytime IConnectionPoint::Advise is called. This class also stores the cookie returned from Advise.


Here is the pseudo code of the class


class _ILegacyComObjectEvents_SinkHelper : _ILegacyComObjectEvents


  //store the cookie returned from the IConnectionPoint::Advise call

  public Int m_dwCookie;

// store the delegates from the user

public _ILegacyComObjectEvents_CanDoSomethingEventHandler m_CanDoSomethingDelegate;


  public _ILegacyComObjectEvents_DoneSomethingEventHandler m_DoneSomethingDelegate;


 ILegacyComObject_SinkHelper ()


                m_dwCookie = NULL;

m_CanDoSomethingDelegate= NULL;

m_DoneSomethingDelegate = NULL;



bool CanDoSomething()


          if (m_CanDoSomethingDelegate ! = NULL)

return m_CanDoSomethingDelegate.Invoke(); //Invoke the delegate

          return 0;   // notice the default value

// See the Notes below for the return type


void DoneSomething()


          if (m_DoSomethingDelegate ! = NULL)

                return m_DoSomethingDelegate.Invoke(); //Invoke the delegate





6) We generate an EventProvider with name <source interface name>_EventProvider

This is the main class which provides the inter-operation between the COM server and the client. Note that it also implements the IDisposable interface.


Here is the pseudoCode.

class _ILegacyComObjectEvents_EventProvider: IDisposable, _ILegacyComObjectEvents_Event


// store the IConnectionPoint object from the COM server here

IConnectionPoint m_ConnectionPoint;


// runtime will provide the implementation of this

IConnectionPointContainer m_ConnectionPointContainer;


//arraylist of object of type SinkHelpers

ArrayList m_aEventSinkHelpers;


public _ILegacyComObject_EventProvider(Object A1)


  m_ConnectionPointContainer = (IConnectionPointContainer)A1;



void Dispose()





void Finalize()


                // save yourself from the threading issues




for (each x in m_aEventSinkHelpers)


  // call unadivse on each connection made





          }//end try

      catch (Exception)



      }//end catch




      }//end finally

}//end of Finalize


// This function gets the IConnectionPoint implementation from the server for the given

// interface. Also intializes the arraylist of EventSinkHelpers.

void Init ()


m_ConnectionPointContainer.FindConnectionPoint(guid of _ILegacyComObject,


                Initialize the arraylist m_aEventSinkHelpers;

}//end of Init


public void add_CanDoSomething(_ILegacyComObjectEvents_CanDoSomethingEventHandler A_1)


//declare some temporary variables.

_ILegacyComObjectEvents_SinkHelper V_0;

// Save yourserlf from the threading issues.




                // first check whether we have IConnectionPoint with us

 if (m_ConnectionPoint==NULL)


V_0 = new _ILegacyComObjectEvents_SinkHelper();

//call the advise method

m_ConnectionPoint.Advise(V_0, &V_1);

// store the delegate and the cookie

V_0.m_dwCookie = V_1;

V_0.m_CanDoSomethingDelegate = A_1;

//add the EventSinkHelper to the arraylist

m_aEventSinkHelpers.Add((object) V_0);

} //end try




}//end finally

}// end add_CanDoSomething


// We have a similar implementation for add_DoneSomething


public void remove_CanDoSomething (_ILegacyComObjectEvents_CanDoSomethingEventHandler A_1)


                //declare some temporary variables

_ILegacyComObjectEvents_SinkHelper V_2;




for (each x in m_aEventSinkHelpers)


                                if ((_ILegacyComObjectEvents_SinkHelper) x.m_CanDoSomethingDelegate == A_1)



                                                V_2 = (_ILegacyComObjectEvents_SinkHelper)x;




 if (m_aEventSinkHelpers is empty)


}//end of try





}//end of remove_CanDoSomething


// We have a similar implementation for remove_DoneSomething


} //end of class _ILegacyComObject_EventProvider



EventProvider does the main work for us. In general every time a user adds a delegate

1)      Get the IConnectionPointer if not available from the COM server and store it in the EvenProvider.

2)      Create a new instance of EvenSinkHelper.

3)      Call Advise and give the EventSinkHelper implementation to the server.


Here is something unexpected which the users will notice.


In COM, a user may create and give one implementation of _ILegacyComObject to the server. When the event occurs the server will call both _ILegacyComObject::CanDoSomething and _ILegacyComObject::DoneSomething. Client calls only one IConnectionPoint::Advise.


However in the managed code the user would need to do something like


private LegacyComObject m_legacyComObject;

m_legacyComObject = new LegacyComObject();

//add the delegate for CanDoSomething

m_legacyComObject.CanDoSomething += m_legacyComObject_CanDoSomething;

//add the delegate for DoneSomething

m_legacyComObject.DoneSomething += m_legacyComObject_DoneSomething;

// m_legacyComObject_CanDoSomething and m_legacyComObject_DoneSomething are two functions implemented in the user class.


Because we treat the two methods as two separate events we need to make two calls to _ILegacyComObjectEvents_EventProvider::add_CanDoSomething and _ILegacyComObjectEvents_EventProvider::add_DoneSomething

Looking at the pseudo code above this would lead to two
separate connections or two separate Advise calls and which is inefficient.

In this case, for example, in the first call we create _ILegacyComObjectEvents_SinkHelper instance and call IConnectionPoint::Advise on it. Also notice that _ILegacyComObjectEvents_SinkHelper::m_DoneSomethingDelegate will remain NULL. If unmanaged COM server invokes this DoneSomething event then we would simply return a default value. The delegates are allowed to return only the primitive types or the value types.


Comments (3)

  1. gcbartlett says:

    Thanks for such a detailed blog.

    I am trying to replace the _EventProvider class produced by tlbimp with one of my own to get around the problem of multiple EventSink subscriptions, but I can’t figure out how to combine the various pieces into a replacement interop assembly.

    So far, I’ve:

    1) generated an interop assembly for my COM object using tlbimp

    2) disassembled this into IL using ildasm

    3) decompiled the IL for the _EventProvider class into C# using the .NET Reflector

    4) removed the _EventProvider class from the IL

    5) replaced the _EventProvider implementation with my own C# code.

    But I don’t know how to recombine the pieces.  I want to assemble the stripped IL and compile the custom C# into one assembly combining references as appropriate.  (If this was C++ and ASM, I would compile/assemble the source modules into OBJ files and then just link them together with the linker).

    If I try to compile/assemble either piece separately I get unresolved references.

    I also tried decompiling the whole interop IL into C#, but ended up with bad source code (our COM object uses multiple indexer properties).

    Any help will be greatly appreciated.



  2. R.D. Holland says:

    gcbartlet, If by multiple event sink objects you mean the issue where each time the user adds an event handler the COM object gets a separate connection point added to it, one can avoid that by implementing the event set directly.

    Either derive your .NET class from the event interface or use the "Implements" keyword. This approach requires that all the methods in the event interface be implemented (they can be stubs) but the result is the COM object only gets one connection added to it.

    Code to connect looks something like this:

    Dim CP as IConnectionPoint

    Dim Cookie as Integer

    Dim EventGuid as Guid

    EventGuid = (GetType(someeventinterface)).GUID

    Dim CPC As IConnectionPointContainer

    CPC = someobjectthatsupportssomeeventinterface



    I write a function that does the above and put the CP and Cookie in my instance data. I just ignore the "_Events" object altogether as well as (in VB) using "Dim withevents …"

    I also think the latest .NET now avoids adding a new connection point for each event delegate added. But I've never tested that. Once I wrote a .NET object that connected to all the events in a large event interface and found that I was being swamped with connection points in my COM object, I went the above route and haven't looked back.

Skip to main content