DllGetClassObject、客戶端測試、DLL 載入/除錯技巧。IClassFactory (類別工廠) 使 CoCreateInstance 可用、實作 IDispatch 支援腳本/晚期繫結、完整雙重介面 (Dual Interface) 實作。
Client: CoCreateInstance(CLSID, ..., IID, &ppv)
→ COM Library: CoGetClassObject → 載入 DLL → DllGetClassObject(CLSID, IID_IClassFactory, &pFactory)
→ pFactory->CreateInstance(NULL, IID, &ppv)
→ ppv 指向介面指標返回 Client
#include "math.h"
#include "MathCOM_i.c"
#include "MathFactory.h"
LONG g_cObjectAndLocks = 0; // 伺服器鎖計數
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
if (rclsid == CLSID_MATHCOM)
{
CMathFactory* pFactory = new CMathFactory;
if (!pFactory) return E_OUTOFMEMORY;
return pFactory->QueryInterface(riid, ppv); // 回傳 IClassFactory
}
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
return (g_cObjectAndLocks == 0) ? S_OK : S_FALSE;
}
// MathFactory.h
class CMathFactory : public IClassFactory
{
public:
CMathFactory() : m_cRef(0) {}
// IUnknown
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IClassFactory
STDMETHODIMP CreateInstance(IUnknown*, REFIID, void**);
STDMETHODIMP LockServer(BOOL);
protected:
LONG m_cRef;
};
// MathFactory.cpp
extern LONG g_cObjectAndLocks;
STDMETHODIMP CMathFactory::CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
*ppv = NULL;
if (pUnkOuter) return CLASS_E_NOAGGREGATION; // 不支援聚合
CMath* pMath = new CMath;
if (!pMath) return E_OUTOFMEMORY;
HRESULT hr = pMath->QueryInterface(riid, ppv);
if (FAILED(hr)) delete pMath;
return hr;
}
STDMETHODIMP CMathFactory::LockServer(BOOL fLock)
{
if (fLock) InterlockedIncrement(&g_cObjectAndLocks);
else InterlockedDecrement(&g_cObjectAndLocks);
return S_OK;
}
// AddRef/Release/QueryInterface 略...
hr = CoCreateInstance(CLSID_MATHCOM, NULL, CLSCTX_INPROC_SERVER,
IID_ISimpleMath, (void**)&pSimpleMath);
IDispatch::Invoke 以 DISPID、VARIANT 參數陣列實現晚期繫結。IUnknown → IDispatch → 自訂介面 (同時支援早期/晚期繫結)。interface ISimpleMath : IDispatch
{
[id(1)] HRESULT Add([in]int nOp1, [in]int nOp2, [out,retval]int* pret);
[id(2)] HRESULT Subtract([in]int nOp1, [in]int nOp2, [out,retval]int* pret);
[id(3)] HRESULT Multiply([in]int nOp1, [in]int nOp2, [out,retval]int* pret);
[id(4)] HRESULT Divide([in]int nOp1, [in]int nOp2, [out,retval]int* pret);
};
interface IAdvancedMath : IDispatch
{
[id(1)] HRESULT Factorial([in]int nOp1, [out,retval]int* pret);
[id(2)] HRESULT Fabonacci([in]int nOp1, [out,retval]int* pret);
};
// IDispatch
STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) { return E_NOTIMPL; }
STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { return E_NOTIMPL; }
STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR** 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);
STDMETHODIMP CMath::Invoke(DISPID id, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS* pd, VARIANT* pVarResult,
EXCEPINFO* pe, UINT* pu)
{
// 參數在 pd->rgvarg[] 中按逆序存放 (最後一個參數在 [0])
if (riid == IID_ISimpleMath)
{
switch (id)
{
case 1: return Add(pd->rgvarg[1].intVal, pd->rgvarg[0].intVal, &pVarResult->intVal);
case 2: return Subtract(pd->rgvarg[1].intVal, pd->rgvarg[0].intVal, &pVarResult->intVal);
case 3: return Multiply(pd->rgvarg[1].intVal, pd->rgvarg[0].intVal, &pVarResult->intVal);
case 4: return Divide(pd->rgvarg[1].intVal, pd->rgvarg[0].intVal, &pVarResult->intVal);
}
}
else if (riid == IID_IAdvancedMath)
{
switch (id)
{
case 1: return Factorial(pd->rgvarg[0].intVal, &pVarResult->intVal);
case 2: return Fabonacci(pd->rgvarg[0].intVal, &pVarResult->intVal);
}
}
return E_NOINTERFACE;
}
IDispatch* pDispatch = NULL;
pSimpleMath->QueryInterface(IID_IDispatch, (void**)&pDispatch);
CComVariant varResult;
CComVariant args[2] = { 10, 4 }; // 逆序: 第2參數, 第1參數
DISPPARAMS disp = { args, NULL, 2, 0 };
hr = pDispatch->Invoke(1, IID_ISimpleMath, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
&disp, &varResult, NULL, NULL);
// varResult.intVal == 14
完成三大里程碑:
CoCreateInstance、CoGetClassObject 快取工廠建立多例。DllCanUnloadNow、LockServer、引用計數正確釋放。下一章將深入探討:早期 vs 晚期繫結效能、ITypeInfo/ITypeLib 自動化 GetIDsOfNames/Invoke、分散式 COM (DCOM) 基礎。
本文配套完整原始碼 (含 MathCOM DLL、TestMathCOM 客戶端、IDL、Factory) 可供下載。