在開發 COM 組件時,常需讓組件回調 C++ 函數。核心難點在於如何將 C++ 回調函數指標正確傳遞給 COM 組件的 SetCallBackFun 方法。本文將詳細拆解實現步驟、代碼示例及常見坑點。
使用 CComPtr<IDispatch> 保存回調函數的派生介面指標:
private:
// 回調函數指標
CComPtr<IDispatch> m_pChangeTempCallbackFun; // 更換模板回調函數
驗證傳入的 VARIANT 類型是否為 VT_DISPATCH,再賦值給智能指標:
bool CDllHelper::SetCallBackFun(VARIANT vaJsFun)
{
if (vaJsFun.vt != VT_DISPATCH)
{
return false;
}
m_pChangeTempCallbackFun = vaJsFun.pdispVal;
return true;
}
定義標準呼叫慣例的回調函數:
void CALLBACK OnChangeTempEvent()
{
// 其他業務代碼
}
由於 SetCallBackFun 接受 VARIANT 參數,需將 C++ 函數指標封裝為 IDispatch 介面。常見做法有二:
CComObject 實現派生介面IDispatch 的類,實現 Invoke 方法內部調用 OnChangeTempEvent。IDispatch 指標。VARIANT,類型設為 VT_DISPATCH,指標指向該介面。IDispatch 動態調用(適用於腳本引擎)若回調源自 JavaScript/VBScript,直接傳遞腳本函數物件即可;若為純 C++,需自行實現 IDispatch 包裝器。
// 1. 定義回調包裝類
class CCallbackWrapper : public IDispatch
{
public:
// IUnknown 方法
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) { ... }
STDMETHOD_(ULONG, AddRef)() { ... }
STDMETHOD_(ULONG, Release)() { ... }
// IDispatch 方法
STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) { return E_NOTIMPL; }
STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { return E_NOTIMPL; }
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }
STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
if (dispIdMember == DISPID_VALUE)
{
OnChangeTempEvent(); // 調用實際回調
return S_OK;
}
return DISP_E_MEMBERNOTFOUND;
}
};
// 2. 客戶端調用代碼
CCallbackWrapper* pWrapper = new CCallbackWrapper();
CComPtr<IDispatch> spDispatch;
pWrapper->QueryInterface(IID_IDispatch, (void**)&spDispatch);
VARIANT var;
VariantInit(&var);
var.vt = VT_DISPATCH;
var.pdispVal = spDispatch;
CDllHelper helper;
helper.SetCallBackFun(var);
CComPtr 正確管理生命週期,避免過早釋放導致崩潰。CoMarshalInterThreadInterfaceInStream 跨線程封送。Invoke 的 DISPPARAMS 需正確填充,並處理 VT_BYREF 等類型。將 C++ 函數指標傳遞給 COM 組件,關鍵在於實現 IDispatch 包裝器並正確構造 VARIANT。掌握此模式,即可實現 COM 與原生 C++ 代碼的雙向通訊。