设置我的 ATL COM 回调函数

Posted

技术标签:

【中文标题】设置我的 ATL COM 回调函数【英文标题】:Setting up my ATL COM callback functions 【发布时间】:2017-08-17 19:36:28 【问题描述】:

this question 的后续,我有以下CerberusNative.idl 文件(这是一个用 Visual C++ 编写的 ATL 项目,它公开了一个 COM 对象):

[
    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);
;

我正在尝试创建一个接口来设置回调方法,该方法从 COM 对象路由回调用者的所述方法的实现。

我正在尝试运行以下代码:

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);

但是,我不确定如何正确设置回调方法。关于如何做到这一点的任何想法?我在 callbacks-&gt;UserExit = prolonguedDisconnection; 之类的行上收到此编译器错误:

错误 C2659 '=':函数作为左操作数

【问题讨论】:

【参考方案1】:

您将ICallbacks 定义为接口,实现ICerberusSession 的对象接受ICallbacks 指针,以便它可以调用对某些事件的回调。这是一个很好的设计并且效果很好。但是,它通常假设您的代码(最后一个代码在底部的 sn-p)与您一样通过CreateInstance 实例化会话对象,而其他带有回调方法的接口在您身边实现。

您的代码实现了一个 COM 对象,该对象又实现了ICallbacks。您创建此类 COM 对象的实例(尤其是没有 CoCreateInstace - 它通常是您身边的客户端代码),然后您将 ICallbacks 接口作为参数传递给 SetCallbacks 调用。请注意,不涉及分配。会话对象应进行调用,例如ICallbacks::UserExit 在提供的指针上,这就是您的代码通过回调接口接收控制的方式 - 您的 ICallbacks 的客户端代码实现调用了它的 UserExit 方法。

【讨论】:

我不确定如何在没有CoCreateInstance 的情况下实例化ICallbacks 并在其上设置功能。在这里看到一些示例代码对我很有帮助。 你定义了一个接口。目前还没有实现——您需要创建它。 CoCreateInstance 通过标识符创建现有实现的实例。在这里,您通常需要在代码中实现一个 COM 对象,而该对象将实现相关的回调接口。给in this question一个非常粗略的例子:CFakeCallback类实现了一个[callback]接口ISampleGrabberCB 谢谢,我想我现在明白了。 请注意,虽然上面的 CFakeCallback 演示了这个想法,但您正在执行 ATL,并且您的实现应该将常规 ATL 类用于 COM 基础,例如 CComObjectRootEx 和朋友。 This question 提到了 CBlah 类,它为 ATL 类实现的元素提供了更好的想法。【参考方案2】:

我只需要在一个单独的类中实现接口:

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;
;

然后,使用它:

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;

【讨论】:

您将在该实施中遇到一大堆问题。您的 AddRef、Release 和 QueryInterface 方法应该正常工作,否则您将泄漏、崩溃、无法使用为 COM 提供的任何语言级工具(例如 CComPtr 或 _com_ptr_t),并编写不符合COM 基本原则。 @MichaelGunter 已更新完成,但可能因为使用方法而无用。

以上是关于设置我的 ATL COM 回调函数的主要内容,如果未能解决你的问题,请参考以下文章

Mongoose - 通过回调函数返回设置虚拟

在 AngularJS 中设置 JSONP 回调函数

框架一种回调函数框架

ActiveX 多线程调用 javascript 回调例程中的问题

JavaScript函数等待回调返回值[重复]

如何在回调函数中为 chrome 扩展图标设置动画?