Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling COM events in C++ (ATL, MFC or pure C++) for Java interop

I'm currently trying to build a C++ library (DLL file) that interfaces with a COM component, to make it usable in Java. The idea was that I'd build a very small C++ DLL with a class that "wraps" around the COM component, and then export it using SWIG. I got pretty far by using an #import statement:

#import "ComponentName.dll"

And calling CoInitialize() and creating an instance of the component (via the IComponentNamePtr class that was generated by Visual Studio). This worked for all the normal COM method calls, which was good.

However, I can't work out how to get events working. I see there's a IComponentNameEventsPtr that complements the main "smart pointer" class, but I couldn't work out what to do to get it working

I've tried all the following to get events working:

  • Pure C++ — I couldn't work out how to do this. I tried creating a new class that inherited from the IComponentNameEvents class, making stubs for all the abstract functions, and overriding the functions, except the functions are not marked as virtual, so that didn't work.
  • MFC — I couldn't get the call to AfxOleInit working correctly. Googling told me that the call fails when called from a DLL as it assumes OLE is already initialised. I wasn't too sure how to fix this. My library crashed every time I tried to create an instance of the COM component (I assume because COM wasn't initialised properly)
  • ATL — I couldn't work out how to do the events in ATL. I could create the class (via the "Simple ATL" wizard, then the "Implement Interface" wizard) but couldn't work out how to use it. I read Using IDispEventImpl on MSDN but couldn't work out how to use the class produced by that howto. Do I need to use the COM object via ATL too (or can I use the #import autogenerated classes)? How do I "attach" the event listener class?
  • I read Event Handling in COM which uses the event_receiver attribute (part of the new Visual C++ unified event model). Originally I couldn't work out how to combine that with usage of the COM component created via the #import statement. I finally worked out (and it was mentioned on the page!) that I need to use the "embedded_idl" flag on the #import statement, but that broke other things (I got a bunch of "expecting a type specification near" errors in the .tlh file)

Does anyone know how to do this? What's the easiest approach to take? My background is in C# and PHP so I don't have much experience with using COM in C++.

tldr: What's the easiest way to use COM events in a C++ DLL?

like image 753
Daniel Lo Nigro Avatar asked Apr 14 '11 10:04

Daniel Lo Nigro


2 Answers

Implement the source interface in your code (using any mechanism, including perhaps generating plain C code using the midl compiler). In your external typelibrary (the one which you are consuming) Look for the interface that looks like:

 [source] interface IOutGoing;

Once you implemented it, register it using Advise on the object that sources the events (unregister it with Unadvise)

Here is a snippet that show a typical use, assuming you went the MIDL way (with ATL/MFC you'd have to write less code but know more macros/templates)

class CSink : public IOutGoing
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

    // IOutGoing
    HRESULT __stdcall GotMessage(int Message);

    CSink() : m_cRef(0) { }
    ~CSink() { }

private:
    long m_cRef;
};


IUnknown* pUnknown;
CoCreateInstance(CLSID_XXXXXXXXX, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&pUnknown);

IConnectionPointContainer* pConnectionPointContainer;
hr = pUnknown->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);

hr = pConnectionPointContainer->FindConnectionPoint(IID_IOutGoing, &pConnectionPoint);

// Instantiate the sink object.
CSink* mySink = new CSink;

// Give the connectable object a pointer to the sink.
DWORD dwCookie;
pConnectionPoint->Advise((IUnknown*)mySink, &dwCookie);
like image 192
sehe Avatar answered Oct 12 '22 22:10

sehe


I actually got this working myself using the Unified Event Model. Some notes from my experience in getting it working:

  • You need to initialise ATL otherwise a null pointer error occurs when hooking up events. If you don't want to use ATL in your project (like I didn't - My project is plain C++), something like this in dllmain.cpp works fine (initialises ATL with a dummy module):

    class CDummyModule : public CAtlDllModuleT<CDummyModule> {};
    CDummyModule _Module;
    
  • You need to add "embedded_idl" to the end of the #import line.

    #import "ComponentName.dll" embedded_idl
    
  • If the component has any structs embedded in it, you may get some MIDL2025 errors ("expecting a type specification near") due to a bug in Visual Studio (see https://connect.microsoft.com/VisualStudio/feedback/details/333473/midl2025-migrating-an-attributed-com-project-to-vs-2008-pro-with-exported-structures and http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/03b78133-5eac-4754-b9af-fc864a9389a3). Solution is to add:

    [importidl("vsbugfix.idl")];
    

And then add a

typedef struct StructName StructName;

For each struct that throws an error

  • Once that's done, you should be able to initialise COM and create an instance of your object. Ugly example code:

    IComponentName blah;
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        MessageBox(NULL, "Failed to initialize COM", "Hi!", 0);
        throw "Failed to initialize COM";
    }
    
    try
    {
        hr = blah.CreateInstance("Something.Something");
        if (FAILED(hr))
        {
            CoUninitialize();
            MessageBox(NULL, "Failed to initialize the COM library!!", "Hi!", 0);
            throw "Failed to initialize the COM library";
        }
    }
    catch (...)
    {
        MessageBox(NULL, "Exception occured when initialising library!", "Error", 0);
        CoUninitialize();
        throw "Exception occured when initialising library!";
    }
    

Once you have your COM object, you can connect events as per the MSDN "Event Handling in COM" article:

__hook(&IComponentNameEvents::OnWhatever, blah, &EventHandlerClass::OnWhatever);

Be sure to unhook all events before calling CoUninitialize() otherwise you'll get errors.

like image 45
Daniel Lo Nigro Avatar answered Oct 12 '22 22:10

Daniel Lo Nigro