When you have a COM automation server your code can invoke methods and properties on the automation object. With a connectable COM object code invocations can go both ways. The consumer invokes methods and the server fires events. To subscribe to these events the client passes an event sink to the server. An event sink is an object which implements an interface with a (type library defined) set of event handling methods. This event sinking mechanism is defined very flexible, a full implementation of the COM IconnectionPointContainer interface works with a collection of multiple types of eventsinks and each sink-type collection can manage any number of sinks.
In the real world the implementation of eventsinks is less elaborate. Notorious is the support in Delphi. A Delphi automation object accepts just one sink of one type. Borland can put the blame on that on Microsoft as the VCL (Delphi class library) code of their implementation is a straight C++ to Object Pascal port of the code found in the MS press book "OLE controls inside out". (Read here for a deeper story) Anyway, the fact that the Delphi automation object supports only one sink will give you a hard time in .NET.
These demo snippets are based on the sample for my COM in Delphi series. It is an in-process form to browse the file system. The automation object fires an event when the user selects another directory. At first sight working with the object in .NET is no big deal.
fm.OnDirectoryChanged += new FileManager.IFileZapperEvents_OnDirectoryChangedEventHandler(DisplayOnLabel);
fm.OnDirectoryChanged += new FileManager.IFileZapperEvents_OnDirectoryChangedEventHandler(AddToListBox);
}
void DisplayOnLabel(string dirName)
{
label1.Text = dirName;
}
void AddToListBox(string dirName)
{
listBox1.Items.Add(dirName);
}
In this snippet a new automation object fm is instantiated and two handlers subscribe to the OnDirectoryChanged event. One to display the current directory on a label, the other to add the history of the directories visited in a listbox. This code will build and run but the moment an event is fired in the automation object an exception is thrown. Each of the methods subscribing to the event takes a separate event sink and the Delphi automation object supports only one. In case only one eventhandler is subscribing the code will run without any problems.
There are two way to work around this. One is to extend the the Delphi framework class. Read here for a full story how to get that done. The other way is to find a workaround in the consuming client which I will describe here.
An event which can sink events to multiple eventhandlers subscribing is a multi cast delegate, an event which can sink only events to one subscriber is a single cast delegate. Delphi events are all single cast, .NET events are all multicast. To turn the single cast delegate in the automation class into a multi cast I will build a new class which inherits from the COM class and redefines the event.
classMyComServer : FileManager.FileZapperClass
{
// Hide the event in the base class as it will fail on multiple event handlers
The class inherits straight form the COM class. Both the OnDirectoryChanged and the OnSelectionChanged event are reintroduced with the new keyword. (You could override the events but this will force you to re-implement all other members as well, something which will not pop up until you try to run the code). These new events are normal .NET multicast delegates (read here for a deeper discussion on the difference between the event and delegate keyword). The class has two private methods to fire the event to all subscribers. These will be fired by the automation object itself. After executing the constructor of the base (COM) class the constructor of the inherited class passes the private members as eventsinks to the event in the COM baseclass.
Now all events are routed through this one event and the automation object only has one sink to take care of. The downside is that the automation object also has to sink events when nobody is actually subscribed to the events. In case that's an unaffordable overhead you have to do some optimization. But I hope you get the idea.