-1

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

Community
  • 1
  • 1
Alexandru
  • 12,264
  • 17
  • 113
  • 208

2 Answers2

3

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.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • I am not sure how to instantiate `ICallbacks` and set up the functions on it without `CoCreateInstance`. Seeing some example code here would be very helpful to me. – Alexandru Mar 24 '17 at 18:40
  • You defined an interface. There is no implementation yet - you need to create it. `CoCreateInstance` creates an instance of already existing implementation by identifier. Here you typically need to implement a COM object in code, and this object would implement the callback interface in question. A very rough example is given [in this question](http://stackoverflow.com/questions/16562479): `CFakeCallback` class implements a [callback] interface `ISampleGrabberCB`. – Roman R. Mar 24 '17 at 19:10
  • Thanks, I think I got it now. – Alexandru Mar 24 '17 at 19:22
  • 1
    Note that while `CFakeCallback` above demonstrates the idea, you are doing ATL and your implementation should rather leverage regular ATL classes for COM base, like `CComObjectRootEx` and friends. [This question](http://stackoverflow.com/a/1506486/868014) mentions class `CBlah` which gives a better idea for elements of ATL class implementation. – Roman R. Mar 25 '17 at 14:43
-2

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;
}
Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • 1
    You're going to encounter a world of problems with that implementation. Your AddRef, Release and QueryInterface methods should work correctly, or else you'll leak, crash, be unable to use any of the language-level tools provided for COM (e.g. CComPtr or _com_ptr_t), and write code that doesn't adhere to COM fundamental principles. – Michael Gunter Mar 24 '17 at 20:36
  • @MichaelGunter Updated for completion, but probably useless because of the method of use. – Alexandru Mar 27 '17 at 16:35