|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionAs 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 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. ImplementationLet'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. 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:
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 The implementation holds a list of listeners subscribed to the event. Subscribing to events using 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 Pay attention to the fact that every method here is declared with an 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. 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 HRESULT CClientTest::EmptyEvent(void) { m_EmptyEventTester = 1; return S_OK; } And last, but definitely not least, 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 InterestOf 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. CreditsThanks Jason Hunt from Noticably Different for ideas and help in writing this article.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||