//
you're reading...
COM Events, Programming Issues/Tips

Tip for Supporting COM Events from a Managed Class : Hide Declaration of Event Members.

1. Introduction.

1.1 I had earlier published Supporting COM Events from a Managed Class. The aim of that article was to impart concise knowledge on how to support COM connection point protocol-based events from a managed class. A set of startup code was also provided for the reader to use as a template.

1.2 This write-up is one of several follow-up articles that are intended to provide additional supplementary tips and techniques useful for writing managed classes that support COM events.

1.3 The theme for the current tip : how to hide managed event accessors.

2. Managed Event Accessors.

2.1 A public event member of a (COM-visible) managed class will be exposed as a pair of COM methods known as event accessors.

2.2 For illustrative purposes, I have listed below the source codes of the C# COM server used in the original Supporting COM Events from a Managed Class article :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CSTestEventFiring
{
    // Define a source interface for COM client objects
    // (e.g. VB6 objects) to implement.
    [ComVisible(true)]
    [Guid("76BBC602-9CBD-40b4-A210-CBB844E7AA70")]
    // The ComInterfaceType.InterfaceIsIDispatch argument
    // for the InterfaceTypeAttribute is important especially
    // for VB6 clients.
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ITestEvents
    {
        // Declare the methods of the ITestEvents source interface.
        [DispId(1)]
        void TestEvent01();
    }

    [ComVisible(true)]
    [Guid("581B3A54-55E0-4e07-8F37-5C20AAC47A25")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    // The ComSourceInterfacesAttribute is the key to
    // exposing COM events from a managed class.
    // Here, we indicate that the CSTestEventFiringClass
    // class will support the ITestEvents source interface.
    [ComSourceInterfaces(typeof(ITestEvents))]
    public class CSTestEventFiringClass
    {
        // A managed delegate must be declared in order
        // to trigger events in the first place.
        // It is the use of the ComSourceInterfacesAttribute
        // together with the ITestEvents argument that links
        // the delegate to the COM event.
        public delegate void TestEvent01_Delegate();
        // Note that the name and signature of each managed event
        // supported by this class must be the same as that
        // of the corresponding method of the source interface.
        //
        // For example, here, the TestEvent01 managed event
        // will correspond to ITestEvents.TestEvent01().
        // They both have the same name and both have the
        // same function return type and parameters.
        //
        // Tip : make sure that the event delegate TestEvent01
        // is declared as private or protected. This will prevent
        // the addition of two extra methods add_TestEvent01()
        // and remove_TestEvent01() from the class interface.
        private event TestEvent01_Delegate TestEvent01;

        // Defined a method to test trigger the TestEvent01 event.
        public void TriggerEvent()
        {
            // As long as a client application has connected
            // with the events of this class, the event delegate
            // TestEvent01 will be non-Null.
            if (TestEvent01 != null)
            {
                TestEvent01();
            }
        }
    }
}

2.3 Observe the declaration for event member “TestEvent01” :

private event TestEvent01_Delegate TestEvent01;

Here I have kept the member as “private”.

2.4 If it had been declared “public”, e.g. :

public event TestEvent01_Delegate TestEvent01;

the following will be the IDL listing for the _CSTestEventFiringClass class interface for the CSTestEventFiringClass coclass :

    [
      odl,
      uuid(6A8B65BB-195B-3D2C-B1D8-D15E8C91EBEA),
      hidden,
      dual,
      nonextensible,
      oleautomation,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSTestEventFiring.CSTestEventFiringClass")    

    ]
    interface _CSTestEventFiringClass : IDispatch {
        [id(00000000), propget,
            custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)]
        HRESULT Equals(
                        [in] VARIANT obj,
                        [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004)]
        HRESULT add_TestEvent01([in] IUnknown* value);
        [id(0x60020005)]
        HRESULT remove_TestEvent01([in] IUnknown* value);
        [id(0x60020006)]
        HRESULT TriggerEvent();
        [id(0x60020007), propget]
        HRESULT iValue([out, retval] long* pRetVal);
        [id(0x60020007), propput]
        HRESULT iValue([in] long pRetVal);
    };

Note the add_TestEvent01() and remove_TestEvent01() methods :

        [id(0x60020004)]
        HRESULT add_TestEvent01([in] IUnknown* value);
        [id(0x60020005)]
        HRESULT remove_TestEvent01([in] IUnknown* value);

These are added by the type library exporter if the TestEvent01 event member was declared as “public”.

2.5 The add_TestEvent01() and remove_TestEvent01() event accessor methods can be seen clearly in the Visual Basic 6.0 Object Browser :

2.6 These event accessors correspond directly to the .NET event accessors which are used to setup and remove event handlers and which are invoked through the “+=” and the “-=” syntices (in C#) or the AddHandler and RemoveHandler statements (in VB.NET).

2.7 It is possible, of course, for an unmanaged COM client application to call add_TestEvent01() to attach an event handler to the TestEvent01() event and remove_TestEvent01() to detach it. However, the required parameter to these methods must be a pointer to the IUnknown interface of a COM-exported .NET delegate object.

2.8 The .NET delegate must somehow be accessed in unmanaged code (e.g. by being instantiated or being returned from some other COM method). In order to be truly useful, it must be able to make a callback to unmanaged code when the event of interest occurs.

2.9 Sounds like a very convoluted way to get hooked to a managed event. Something you may not want to expose your clients to.

3. How to Hide Event Method Declarations.

3.1 In order to shield your COM client developers from the presence of these add_*() and remove_*() accessors, simply make sure that the event members of the managed class are never declared “public”.

3.2 They should be declared as either “private” or “protected”.

4. In Conclusion.

4.1 COM event firing from a managed class should idealy be done with CLR support via the ComSourceInterfacesAttribute.

4.2 However, it would be interesting to be notified of events outside the aegis of the COM connection point protocol. I intend to do further research to use add_*() and remove_*() event accessors in conjunction with .NET delegates accessed from unmanaged code.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: