Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sinking events from managed code in unmanaged C++

0.00/5 (No votes)
24 Apr 2008 1  
Raising events in managed code and sinking it in unmanaged C++.

Introduction

As time goes by, some parts of the big project I am working on are translated to .NET from native C++. One of the first modules that was translated is a lower level module that should be used by an unmanaged code application. This raised an interesting problem. The lower level library uses Interop to wrap itself in the COM shell. Since the library works with asynchronous I/O, it can raise events. The only example usage for C++ code I've found is an extension of a Microsoft article, How to: Raise Events Handled by a COM Sink. The full article contains not only a VB6 sink example but also C++ sinks: COM Interop: Really Raising Events Handled by a COM Sink.

The only problem with all those examples is that none of them worked for me. The ATL/C++ example threw an AccessViolationException in the managed .NET object whenever the delegate was invoked. The control never reached the handling function in the C++ sink. If not handled, an AccessViolationException was returned as the E_POINTER (0x80004003) result to the COM client.

After a week of suffering and trying to find a solution that will make delegates work, I turned for help to Jason Hunt of the above article. The only solution that was brought up was to do all the work manually. This was not as hard as it sounded.

Implementation

Let's start with the implementation. First of all, let's look at the interface of the events handler. This interface is defined by the .NET object and will have to be implemented by the COM client:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHehServerEvents
{
    void EmptyEvent();
}

The interface describes a single event handler which will be raised by the .NET component. InterfaceType is set to IUnknown for simplicity, so the COM client will have a smaller interface to implement.

Let's continue with the interface. It is possible to expose the actual implementation of the server, but it's cleaner to define only a narrow, well defined interface:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHehServer
{
    void TestAddListener(IHehServerEvents evt);
    void TestEvent();
}

Our COM object implemented in .NET will expose only two methods:

  • TestAddListener - allows to add one more listener to the event.
  • TestEvent - initiates the event.

And now, the real stuff, the actual implementation of the class raising the event:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class HehServer : IHehServer
{
    public List<IHehServerEvents> m_listeners = new List<IHehServerEvents>();

    public void TestEvent()
    {
        foreach (IHehServerEvents evt in m_listeners)
        {
            evt.EmptyEvent();
        }
    }

    public void TestAddListener(IHehServerEvents evt)
    {
        m_listeners.Add(evt);
    }
}

First of all, the type of the interface hides the implementation of the server by setting its ClassInterfaceAttribute to ClassInterfaceType.None. The client will use it only via the interface implemented: IHehServer. Pay attention that there is no signs that this implementation throws any kind of events like it is done in every other example using delegates: ComSourceInterfacesAttributes is omitted.

The implementation holds a list of listeners subscribed to the event. Subscribing to events using TestAddListener just adds the listener to the list. This way, when the event is called, the implementation just has to go over the list and call every listener.

When building, don't forget to compile the component as a DLL visible to COM. In .NET Project Properties, set "Output type" to "Class Library", then enter "Assembly Information", and select "Make assembly COM-Visible". I also use the "Register for COM Interop" option in the "Build" tab to integrate the component more tightly with Visual Studio.

Now, let's use the implemented class. We are going over to the dark side: C++. One of the headers should include a reference to the compiled TLB file:

#import "..\comserver\bin\Debug\comserver.tlb" \
    raw_interfaces_only, named_guids, no_namespace

And, here is the class declaration:

class CClientTest : public IHehServerEvents
{
private:
    IHehServerPtr m_server;
    int m_EmptyEventTester;
public:
    CClientTest(void);
    ~CClientTest(void);
    void TestEvent(void);
    HRESULT __stdcall QueryInterface(const IID &, void **);
    ULONG __stdcall AddRef(void) { return 1; }
    ULONG __stdcall Release(void) { return 1; }
    HRESULT __stdcall EmptyEvent(void);
};

First of all, the class inherits from the events interface IHehServerEvents which makes it a listener. Since IHehServerEvents has an IUnknown parent from the point of view of the C++ compiler, we are required to implement the QueryInterface, AddRef, and Release methods. This class is not a real COM object that should count its references, so it is enough to just return a dummy value for adding and removing references to the object. The only method that should be implemented "for real" is QueryInterface. And, of course, each one of the event handling methods should be implemented by this class.

Pay attention to the fact that every method here is declared with an __stdcall attribute because this is the way COM works. It is possible to use ATL's STDMETHOD/STDMETHODIMP macros here.

The implementation of the class is fairly simple. First of all, we need to instantiate a COM object that throws the event:

CClientTest::CClientTest(void)
{
    HRESULT hr = S_OK;
    hr = m_server.CreateInstance(__uuidof(HehServer));
    ATLASSERT(SUCCEEDED(hr));
}

Here, the .NET provided libraries are used. IHehServerPtr has methods that allow creating relevant instances of the underlying class. Note that the GUID of the created class is the identification of the implementation and not the interface.

Using the class is also very simple:

void CClientTest::TestEvent(void)
{
    m_server->TestAddListener(this);
    m_EmptyEventTester = 0;
    m_server->TestEvent();
    ATLASSERT(m_EmptyEventTester != 0);
}

First, the class subscribes itself for events of the .NET component. And then, the TestEvent method is being called. The only thing the handler of the event is doing is change the value of the flag used for testing:

HRESULT CClientTest::EmptyEvent(void)
{
    m_EmptyEventTester = 1;
    return S_OK;
}

And last, but definitely not least, TestAddListener accepts an IHehServerEvents argument while our class is of a different type. Since we are working in the COM world, simple casting is not and the QueryInterface should be used:

HRESULT CClientTest::QueryInterface(const IID & iid,void ** pp)
{
    if (iid == __uuidof(IHehServerEvents) || iid == __uuidof(IUnknown))
    {
        *pp = this;
        AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

Viola! We have our callbacks from the COM code.

Points of Interest

Of cause, a real world implementation should include error checking and an option to unsubscribe from the events, but this is a pretty simple task after the principles are understood. If I am wrong, leave a comment, and I will see what can be done about it. This exercise provides one more proof that you can not always rely on the tools provided by compilers and platforms. Sometimes, you just have to perform the work yourself.

Credits

Thanks Jason Hunt from Noticably Different for ideas and help in writing this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here