Search code examples
c++visual-c++comatlidl

Setting up my ATL COM callback functions


A follow-up to this question, I have the following CerberusNative.idl file (this is an ATL project written in Visual C++ which exposes a COM object):

[
    object,
    uuid(AECE8D0C-F902-4311-A374-ED3A0EBB6B49),
    nonextensible,
    pointer_default(unique)
]
interface ICallbacks : IUnknown
{
    [id(1)] HRESULT UserExit([in] int errorCode, [in] BSTR errorMessage);
    [id(2)] HRESULT UserAttemptingReconnection();
    [id(3)] HRESULT UserReconnected();
};

[
    object,
    uuid(B98A7D3F-651A-49BE-9744-2B1D8C896E9E),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface ICerberusSession : IDispatch {
    ...
    [id(5)] HRESULT SetCallbacks([in] ICallbacks* callbacks);
};

I am attempting to create an interface for setting up callback methods which route from the COM object back to an implementation of said methods from the caller.

I am trying to run the following code:

HRESULT prolonguedDisconnection(int code, BSTR *message) {
    std::wcout << code << ": " << message << std::endl;
}
HRESULT reconnecting() {
    std::wcout << "Reconnecting." << std::endl;
}
HRESULT reconnected() {
    std::wcout << "Reconnected." << std::endl;
}
...
CoInitialize(NULL);
CerberusNativeLib::ICallbacksPtr callbacks;
callbacks.CreateInstance(__uuidof(CerberusNativeLib::ICallbacks));
callbacks->UserExit = prolonguedDisconnection;
callbacks->UserAttemptingReconnection = reconnecting;
callbacks->UserReconnected = reconnected;
CerberusNativeLib::ICerberusSessionPtr session;
session.CreateInstance(__uuidof(CerberusNativeLib::CerberusSession));
session->SetCallbacks(callbacks);

However, I am not sure how to properly set up the callback methods. Any ideas on how to do this? I get this compiler error on lines such as callbacks->UserExit = prolonguedDisconnection;:

Error C2659 '=': function as left operand


Solution

  • I needed simply to implement the interface in a separate class:

    class Callbacks : public CerberusNativeLib::ICallbacks {
    
    #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
            const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
    
    MIDL_DEFINE_GUID(IID, IID_ICallbacks, 0xAECE8D0C, 0xF902, 0x4311, 0xA3, 0x74, 0xED, 0x3A, 0x0E, 0xBB, 0x6B, 0x49);
    
    public:
    
        HRESULT(*user_exit) (int, BSTR) = NULL;
        HRESULT(*user_attempting_reconnection) () = NULL;
        HRESULT(*user_reconnected) () = NULL;
    
        Callbacks::Callbacks(HRESULT(*disconnected)(int, BSTR), HRESULT(*reconnecting)(), HRESULT(*reconnected)()) : m_cRef(0) {
            user_exit = disconnected;
            user_attempting_reconnection = reconnecting;
            user_reconnected = reconnected;
        }
    
        HRESULT __stdcall UserExit(int ErrorCode, BSTR ErrorMessage) {
            return user_exit(ErrorCode, ErrorMessage);
        }
        HRESULT __stdcall UserAttemptingReconnection() {
            return user_attempting_reconnection();
        }
        HRESULT __stdcall UserReconnected() {
            return user_reconnected();
        }
        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) {
            if (!ppvObject)
                return E_INVALIDARG;
            *ppvObject = NULL;
            if (riid == IID_IUnknown || riid == IID_ICallbacks)
            {
                *ppvObject = (LPVOID)this;
                AddRef();
                return NOERROR;
            }
            return E_NOINTERFACE;
        }
        ULONG STDMETHODCALLTYPE AddRef() {
            InterlockedIncrement(&m_cRef);
            return m_cRef;
        }
        ULONG STDMETHODCALLTYPE Release() {
            ULONG ulRefCount = InterlockedDecrement(&m_cRef);
            if (0 == m_cRef)
                delete this;
            return ulRefCount;
        }
    
    private:
        ULONG m_cRef;
    };
    

    Then, to use it:

    HRESULT result = CoInitialize(NULL);
    if (result != S_OK)
    {
        std::wcout << "Failed to initialize the COM library on the current thread or to identify the concurrency model as single-thread apartment (STA)." << std::endl;
        std::wcout << result << ": " << _com_error(result).ErrorMessage() << std::endl;
        std::wcout << "Press the enter key to exit." << std::endl;
        std::cin.get();
        return 0;
    }
    Callbacks *callbacks = new Callbacks(&prolonguedDisconnection, &reconnecting, &reconnected);
    CerberusNativeLib::ICerberusSessionPtr session;
    result = session.CreateInstance(__uuidof(CerberusNativeLib::CerberusSession));
    if (result != S_OK)
    {
        std::wcout << "Failed to create a new Cerberus session." << std::endl;
        std::wcout << result << ": " << _com_error(result).ErrorMessage() << std::endl;
        std::wcout << "Press the enter key to exit." << std::endl;
        std::cin.get();
        return 0;
    }
    result = session->SetCallbacks(callbacks);
    if (result != S_OK)
    {
        std::wcout << "Failed to set Cerberus session callbacks." << std::endl;
        std::wcout << result << ": " << _com_error(result).ErrorMessage() << std::endl;
        std::wcout << "Press the enter key to exit." << std::endl;
        std::cin.get();
        return 0;
    }