Click here to Skip to main content
Click here to Skip to main content
Go to top

Sinking events from managed code in unmanaged C++

, 23 Apr 2008
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Uri Kogan
Software Developer
Israel Israel
No Biography provided

Comments and Discussions

 
Questionhow can i pass some parameter to receive application(client)? [modified] Pinmemberhan dol5-Feb-12 20:26 
AnswerRe: how can i pass some parameter to give application(client)? PinmemberUri Kogan5-Feb-12 20:43 
GeneralRe: how can i pass some parameter to give application(client)? Pinmemberhan dol6-Feb-12 0:22 
Wink | ;) thank you for your comments.
i try to modify your souce like folowing code.
 
[C#]
 
namespace comserver
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHehServerEvents
{
[DispId(1)]
void EmptyEvent(int nParam);
}
 
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHehServer
{
[DispId(2)]
void TestEvent(/*int nParam*/);
 
[DispId(3)]
void TestAddListener(IHehServerEvents evt);
}
 
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class HehServer : IHehServer
{
public List m_listeners =
new List();
 
public void TestEvent(/*int nParam*/)
{
// int nRtn = nParam;
foreach (IHehServerEvents evt in m_listeners)
{
evt.EmptyEvent(101);
}
}
 
public void TestAddListener(IHehServerEvents evt)
{
m_listeners.Add(evt);
}
 
}
}
 
[ .h ]
 
class CClientTest : public IHehServerEvents
{
private:
IHehServerPtr m_server;
 
int m_EmptyEventTester;
 
public:
 
CClientTest(void);
~CClientTest(void);
 
void TestEvent();
char * GetValue();
 
HRESULT __stdcall QueryInterface(const IID &,void **);
ULONG __stdcall AddRef(void) { return 1; }
ULONG __stdcall Release(void) { return 1; }
 
HRESULT __stdcall EmptyEvent(long nParam);
};
 
[ .cpp ]
 
HRESULT CClientTest::EmptyEvent(long nParam)
{
m_EmptyEventTester = 1;
return S_OK;
}
 
above code is not run.
what's the problem?
 
TestEvent()-->ATLASSERT(m_EmptyEventTester != 0);
 
that have to alert message "m_EmptyEventTester == 0"
 
what's wrong? help me!
 
thank you
GeneralRe: how can i pass some parameter to give application(client)? PinmemberUri Kogan6-Feb-12 2:24 
GeneralRe: how can i pass some parameter to give application(client)? Pinmemberhan dol8-Feb-12 23:44 
GeneralRe: how can i pass some parameter to give application(client)? PinmemberUri Kogan11-Feb-12 19:50 
GeneralHandling Same Event Between CSharp COM DLL(Registeration Free) and C++ Application.EXE PinmemberMrKyaw5-Apr-11 16:39 
GeneralRe: Handling Same Event Between CSharp COM DLL(Registeration Free) and C++ Application.EXE PinmemberUri Kogan6-Apr-11 0:13 
GeneralThanks! PinmemberMember 218256613-Aug-09 6:08 
Generalhi Pinmembervvidov10-Oct-08 5:26 
GeneralRe: hi PinmemberUri Kogan10-Oct-08 9:14 
GeneralThanks a lot! PinmemberPrashant SL24-Sep-08 1:30 
GeneralQueryInterface PinmemberChristian Perner4-Sep-08 23:11 
GeneralRe: QueryInterface PinmemberUri Kogan4-Sep-08 23:48 
GeneralRegistration of server-class PinmemberChristian Perner4-Sep-08 21:06 
AnswerRe: Registration of server-class PinmemberUri Kogan4-Sep-08 21:22 
GeneralRe: Registration of server-class [modified] PinmemberChristian Perner4-Sep-08 22:29 
GeneralRe: Registration of server-class Pinmemberliaohaiwen21-Oct-08 21:55 
GeneralRe: Registration of server-class PinmemberUri Kogan21-Oct-08 21:59 
GeneralRe: Registration of server-class Pinmemberliaohaiwen21-Oct-08 22:25 
GeneralRe: Registration of server-class PinmemberChristian Perner22-Oct-08 1:12 
GeneralRe: Registration of server-class Pinmemberliaohaiwen22-Oct-08 19:29 
GeneralRe: Registration of server-class PinmemberChristian Perner26-Oct-08 20:41 
QuestionGreat work, thanks. Pinmemberhuiliu4-Jun-08 11:45 
AnswerRe: Great work, thanks. PinmemberUri Kogan4-Jun-08 18:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140905.1 | Last Updated 24 Apr 2008
Article Copyright 2008 by Uri Kogan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid