.NET Delegate Event Model vs COM Connection Points

Environment: .NET Framework
SDK Beta 1, Visual C++ 6 SP4

Introduction :

In this article, we’ll take a dip into how the
Connection Points Event Handling mechanism in Classic COM Components
can be used by .NET applications to receive event Notifications via the COM
Interop. What’s interesting here is that, .NET applications can continue
to use the Delegate Event Model to subscribe to &
receive event notifications.The COM Interop acts an adaptor that glues both these
models together so that they interoperate seamlessly.You might also want to
look up the article
Understanding Classic COM interoperability with .NET applications

for an introduction on how Classic COM components interoperate with the .NET framework.


How the Connection Point Event Handling mechanism in Classic COM maps to the Delegate event handling mechanism in .NET


Click here for larger image


Figure #1: How the Connection Point Event Handling mechanism in Classic COM maps to the
Delegate
event handling mechanism in .NET

A primer on Connection Points :

The Connection Points event handling mechanism, as you know,
is one of the primary enablers for bi-directional communication between your COM
components and the consumers of your components.Just to jog your memory, I’ll brief
you a little bit on the event handling mechanism in Classic COM Components.
Typically, COM components that support event notifications have what is called,
an outgoing interface. The outgoing interface is used by the component
to call into the client when a specific event has occured. Outgoing interfaces
are marked with the [source] attribute in the coclass section
of the component’s IDL file. The [source] attribute in the IDL
allows development tools and IDEs to parse the typelibrary to check to see if
the object supports an outgoing interface. Consumers or clients of these
components usually set up a sink object that implements this outgoing
interface. An interface pointer to this sink
object is passed by the consumer to the component. The component stashes away
this interface pointer in typically something like a map that contains a list of
outgoing interface pointers to sink objects that are interested in receiving
notifications from the component. Whenever a component needs to raise an event,
it uses the map to get a list of interface pointers to the sink objects that
have subscribed for notifications. It then notifies them by calling the
respective method on the outgoing interface that’s implemented by the sink
object.






Connection Points in Classic COM


Essentially, the COM object that
supports outgoing interfaces, implements the
IConnectionPointContainer interface. So, a client that wants to
receive event notifications does a QI on the COM object for the
IConnectionPointContainer interface to see if it supports
outgoing interfaces. If the QI fails, then the object
does not support events. If the QI succeeds, the client
calls the FindConnectionPoint method (could also call
EnumConnectionPoints) on the IConnectionPointContainer
interface by passing it the IID of the outgoing
interface. If such an interface is supported, the client receives
back an IConnectionPoint interface pointer corresponding to the
outgoing interface. It then calls the
IConnectionPoint::Advise method and passes to it, the
sink object’s IUnknown pointer. The COM object adds this
IUnknown pointer to its map to keep a list of the sink objects
that have subscribed for notifications. The client gets back a
cookie from the COM object that it can subsequently use to revoke
event notifications. When the COM object needs to raise events,
it iterates through the map, gets a list of all the sink object
interface pointers
and calls the corresponding event methods on the
outgoing interface that’s implemented by the sink object. When a
client no longer desires to receive notifications, it removes itself from
the object’s map by calling the IConnectionPoint::Unadvise
method by passing it the cookie that it received earlier on
the IConnectionPoint::Advise call.

Connection Points in Classic COM
Figure #2: Connection Points in Classic COM


In simple terms, that’s how the event handling mechanism & bi-directional communication
works in Classic COM components. I’ll try not to explain the Connection Points event-handling
infrastructure in COM in any more detail. Most books that teach COM programming usually have
a full chapter dedicated to explain this architecture and you might want to look them up to
further your understanding in this topic.

Connection Points Vs Delegates :

Now, let’s take a look at how the Connection Points event
handling mechanism in COM translates to the delegate event handling mechanism in
the .NET world. We’ll take a look at how you can use .NET managed event sinks to
catch event notifications sent from COM objects. To get started, let’s first
take a look at the COM object, that’s going to source events to your .NET
application. Let’s put together a simple COM object that will page your .NET
application whenever an airline arrives at a fictitious Airport called John Doe
International Airport. We will subscribe to this paging service from our .NET
application so that we get paged whenever an airplane taxies down on John Doe’s
runway.


AirlineArrivalPager Classes (Class View)


We’ll create an ATL EXE project that
hosts an object called AirlineArrivalPager
.
The AirlineArrivalPager object
supports an incoming interface called IAirlineArrivalPager and
an outgoing interface called
_IAirlineArrivalPagerEvents. Here’s the interface definition of the
_IAirlineArrivalPagerEvents outgoing interface. This interface is
marked with the [source]attribute in the coclass definition.


interface IAirlineArrivalPager : IDispatch
{
[id(1), helpstring(“method AddArrivalDetails”)]
HRESULT AddArrivalDetails([in] BSTR bstrAirlineName,
[in] BSTR bstrArrivalTerminal);
};
….

dispinterface _IAirlineArrivalPagerEvents
{
properties:
methods:
[id(1), helpstring(“method OnAirlineArrivedEvent”)]
HRESULT OnAirlineArrivedEvent([in] BSTR bstrAirlineName,
[in] BSTR bstrArrivalTerminal);
};

….

coclass AirlineArrivalPager
{
[default] interface IAirlineArrivalPager;
[default, source] dispinterface _IAirlineArrivalPagerEvents;
};


Take a look at the implementation of the
incoming IAirlineArrivalPager interface’s AddArrivalDetails
method:


STDMETHODIMP CAirlineArrivalPager::AddArrivalDetails(BSTR bstrAirlineName,
BSTR bstrArrivalTerminal)
{
// Notify all subscribers that an Airline
// has hit the tarmac
Fire_OnAirlineArrivedEvent(bstrAirlineName,
bstrArrivalTerminal);

// Return the status to the caller
return S_OK;
}

The implementation of this method uses
the Fire_OnAirlineArrivedEvent helper method to notify all sink objects
implementing _IAirlineArrivalPagerEvents that have subscribed for
event notifications. The
Fire_OnAirlineArrivedEvent is a method of the helper proxy class
derived from IConnectionPointImpl that is generated automatically by
the ATL Implement Connection point wizard. Essentially, it iterates
through the map where it stored the interface pointers to the sink objects when
IConnectionPoint::Advise was called and uses these interface pointers
to call the event notification method (OnAirlineArrivedEvent)
implemented by the client’s sink object.


If you were a C++ programmer
coding a COM aware client application to receive notifications, you would set up
a sink object in the client application that implements the
_IAirlineArrivalPagerEvents interface. You would then create the
AirlineArrivalPager object and pass it the IUnknown interface
pointer of the sink object through a call to
IConnectionPoint::Advise or use a helper method such as
AtlAdvise to wire your sink to the source object so that you
can receive event notifications. With VB, it’s as simple as using the
WithEvents keyword in your declaration and defining a handler function
for receiving notifications. VB will do all the hard work under the covers to
wire the notifications made on the outgoing interface to the appropriate
handler function. Event handling in .NET is primarily based on the Delegate
Event model
. A delegate is something akin to function pointers
that we use in C/C++. The delegate based Event model was popularized by
the simplicity of its use right from the WFC (Windows Foundation Classes in
Visual J++) days. Delegates allow an event raised by any component to be connected to
a handler function or method of any other component as long as the function signatures
of the handler function or method matches the exact signature of that of the event being raised.
Take a look at this simple example below that shows you how you can put delegates to action :


// Here’s the SayGoodMorning delegate
delegate string SayGoodMorning();

public class HelloWorld
{
public string SpeakEnglish() {
return “Good Morning”;
}

public string SpeakFrench() {
return “Bonjour”;
}

public static void Main(String[] args) {

HelloWorld obj = new HelloWorld();

// Associate the delegate with a method reference
SayGoodMorning english = new SayGoodMorning(obj.SpeakEnglish);
SayGoodMorning french = new SayGoodMorning(obj.SpeakFrench);

// Prints the following :
// Good Morning
// Bonjour
System.Console.WriteLine(english());
System.Console.WriteLine(french());
}

}/* end class */


In the example above, we declare a
delegate called SayGoodMorning. Then we wire the delegate to
reference the SpeakEnglish and SpeakFrench methods of the
HelloWorld object. All that is required is that the
SpeakEnglish and SpeakFrench methods have the same signature
as that of the SayGoodMorning delegate. The reference is typically
made by instantiating the delegate as if it were an object and passing in
the referenced method as its parameter. This parameter could be an object’s method
or could be even be a static function. The delegate
maintains the reference it needs to call the right handler for the event. So delegates
are first class object-oriented citizens and are also type-safe and secure to deal
with. The .NET event-handling model is based primarily on the delegate event
model
. Take a look at the following example:


// Create a button
WinForms.Button AngryButton = new WinForms.Button ();

// Add a delegate to the button’s Click event
AngryButton.Click += new System.EventHandler(AngryButton_Click);

// Here’s the handler function that the delegate references
protected void AngryButton_Click(object sender,EventArgs e)
{
MessageBox.Show(“Please Stop clicking me !!”);
}


When your application deals with controls
and wants to receive specific notifications, it creates a new instance of an
EventHandler delegate that contains a reference to the actual
handler function that will handle the events raised by the control. In the
example shown above, the EventHandler delegate contains a reference to
the AngryButton_Click method. The AngryButton_Click method
needs to have the same method signature as that of the EventHandler
delegate
. Here’s how the signature of the System.EventHandler
delegate looks like:


public delegate void EventHandler(object sender, EventArgs e);


The EventHandler delegate
instance will then have to be added to the control’s Event list. When the
control raises an event, all the delegates that have been added to the event
list will be invoked and each delegate will route it to the correct
handler function that it references. In our example,whenever the Click
Event occurs in the button the call will be routed to the
AngryButton_Click method. I guess this gives you a fairly good idea on
how delegates play their role in the event-handling infrastructure in the .NET framework.
The reason, I explained to you how delegates work is because it’s one of the primary enablers
of the .NET event handling model and it’s important to understand this to appreciate
how NET applications use delegates to subscribe to event notifications from Classic COM Components.

VB Client - Control Tower App


Here’s a simple VB Client application that
assumes the role of the Control Tower at John Doe International Airport and
calls the AddArrivalDetails method in the incoming interface.
The implementation of this method in turn triggers the event notifications that
are subsequently caught by the handler functions in the .NET application that
have subscribed for event notifications for the OnAirlineArrivedEvent
event. The AirlineArrivalPage COM object is itself a singleton
object hosted in an out-of-proc COM server. So the same instance of the
object services both the VB based Control tower application (that triggers
events) and the .NET pager applications that have subscribed for
OnAirlineArrivedEvent event notifications.

Dim AirlinePager As New AIRLINENOTIFYLib.AirlineArrivalPager

Private Sub AirlineArrived_Click()
AirlinePager.AddArrivalDetails Me.AirlineName, Me.ArrivalTerminal
End Sub


With that said, let’s see how a .NET
managed application receives event notifications generated by the
AirlineArrivalPager COM object. Firstly, you need to generate a .NET
metadata proxy from the COM objects’ typelibrary so that it can be consumed by a
.NET application. So let’s use the Type Library Importer(TLBIMP)
to generate the metadata assembly for us.

tlbimp AirlineNotify.tlb /out:AirlineNotifyMetadata.dll


This metadata proxy will be referenced in
your .NET application. Here’s a simple .NET WinForms application that subscribes
to event notifications from the AirlineArrivalPager COM Component by
using a delegate.

WinForms Pager Application



using AIRLINENOTIFYLib;

public class AirlineNotifyForm : System.WinForms.Form
{
private System.WinForms.CheckBox checkBoxPaging;
private System.WinForms.ListBox listPager;
private AirlineArrivalPager m_pager = null;

public AirlineNotifyForm() {

// Subscribe to event notifications from
// the AirlineArrivalPager component
subscribePaging();
}

void subscribePaging() {
// Create an AirlineArrivalPager object
m_pager = new AirlineArrivalPager();

// Add the delegate referencing the OnMyPagerNotify method
// to the OnAirlineArrivedEvent event list (ICP::Advise)
m_pager.OnAirlineArrivedEvent +=
new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);

}/* end subscribePaging */

protected void checkBoxPaging_CheckedChanged (object sender, System.EventArgs e) {
if(checkBoxPaging.Checked) {
// If checked, register the delegate referencing OnMyPagerNotify
// to the OnAirlineArrivedEvent event list (ICP::Advise)
m_pager.OnAirlineArrivedEvent +=
new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);
}
else {
// If Unchecked, remove the delegate referencing OnMyPagerNotify
// from the OnAirlineArrivedEvent event list (ICP::Unadvise)
m_pager.OnAirlineArrivedEvent -=
new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);
}
}/* end checkBoxPaging_CheckedChanged */

public void OnMyPagerNotify(String strAirline, String strTerminal) {
StringBuilder strDetails = new StringBuilder(“Airline “);
strDetails.Append(strAirline);
strDetails.Append(” has arrived in “);
strDetails.Append(strTerminal);
listPager.InsertItem(0,strDetails);
}/* end OnMyPagerNotify */
}/* end class */


The
line of code that is most important here is the line:



m_pager.OnAirlineArrivedEvent +=
new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);


If you understand the semantics of how
delegates work, you should have absolutely no problem comprehending what’s going
on here. What you’re doing is adding the
_IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler delegate
to the OnAirlineArrivedEvent event list. The delegate references the
OnMyPagerNotify method. Usually the name of the event
(OnAirlineArrivedEvent) is the same as the method name in the outgoing
interface. The delegate name
(_IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler) usually
follows the pattern
InterfaceName_EventNameEventHandler. That’s all there
is to receiving event notifications from COM components. All you need to do is
create an instance of the component and then add a delegate referencing
your handler function to the event list. Effectively, what you are doing here is
something that’s analogous to the IConnectionPoint::Advise in the COM
world. Whenever, the OnAirlineArrivedEvent event is raised by the COM
component, the OnMyPagerNotify method will be called to handle the event notification. It’s that simple in
.NET, to wire a handler sink to receive event notifications from a COM object that sources events.


When you no longer want to receive notifications, you
can remove the delegate from the event list by calling:


m_pager.OnAirlineArrivedEvent -=
new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);


This is analogous to the
IConnectionPoint::Unadvise method call that revokes further
notifications by removing your sink object’s interface pointer from the map
using the cookie that you received in the Advise call.So who
handles the mapping between the Connection point event handling model in
Classic COM
and delegate event model in .NET?. The metadata proxy
generated by the Typelibrary importer(TLBIMP) contains classes that act
as adaptors to wire the Connection point Event Model in the unmanaged
world to the Delegate based event model in the .NET world via the RCW
stub
that is created at runtime. If you are interested in examining what
happens under the hood, I encourage you to open up the metadata proxy
(AirlineNotifyMetadata.dll) using the IL Disasembler (ILDASM) and examine
the MSIL code for the various methods in the helper classes.

Conclusion :


I hope this article will get you started on how you can use your .NET applications to
receive event notifications from Classic COM Components. From a programmer’s
perspective, you get to leverage the simplicity and ease of use of the .NET Delegate
event model to receive event notifications from Classic COM Components via the COM Interop.
Yet again, the COM Interop shields us from the nuts and bolts of having to wire
these two models together.So there you go, yet another testimony to the fact that ‘COM
is not dead’ and will continue to live in harmony with the über-powerful .NET world.

Downloads

Download demo project – 31 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read