Setting up my ATL COM callback functions

2019-09-20 19:32发布

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

2条回答
聊天终结者
2楼-- · 2019-09-20 20:04

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;
}
查看更多
Rolldiameter
3楼-- · 2019-09-20 20:15

You defined ICallbacks as an interface and the object that implements ICerberusSession accepts a ICallbacks pointer so that it could invoke calls back on certain events. This is a good design and works well. However, it usually assumes that your code (last code snippet at the bottom) instantiates session object via CreateInstance as you do, and the other interface with callback methods is implemented on your side.

Your code implements a COM object, which in turn implements ICallbacks. You create an instance of such COM object (esp. without CoCreateInstace - it's typically client code on your side) and you pass ICallbacks interface as an argument in SetCallbacks call. Note that there is no assignment involved. Session object is expected to do a call, e.g. ICallbacks::UserExit on supplied pointer and this is how your code receives control through the callback interface - your client side code implementation of ICallbacks has its UserExit method called.

查看更多
登录 后发表回答