首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

COM 技術深度探索 (三):打造真正的 COM 元件 — 類別工廠、IDispatch 與雙重介面

     編輯:關於VC++

系列回顧

  • 第 1 部分:COM 基礎、從 C++ 到 DLL 再到 COM、IUnknown、簡易 COM 物件模型。
  • 第 2 部分:純手工建立 DLL 型 COM、IDL 定義、註冊機制、DllGetClassObject、客戶端測試、DLL 載入/除錯技巧。
  • 本文 (第 3 部分):實作 IClassFactory (類別工廠) 使 CoCreateInstance 可用、實作 IDispatch 支援腳本/晚期繫結、完整雙重介面 (Dual Interface) 實作。

一、實作 IClassFactory — 讓 CoCreateInstance 生效

COM 物件建立流程

Client: CoCreateInstance(CLSID, ..., IID, &ppv)
    → COM Library: CoGetClassObject → 載入 DLL → DllGetClassObject(CLSID, IID_IClassFactory, &pFactory)
    → pFactory->CreateInstance(NULL, IID, &ppv)
    → ppv 指向介面指標返回 Client

修改 MathCOM.cpp

#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 / .cpp — 類別工廠實作

// 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 略...

客戶端簡化 — 直接用 CoCreateInstance

hr = CoCreateInstance(CLSID_MATHCOM, NULL, CLSCTX_INPROC_SERVER,
                      IID_ISimpleMath, (void**)&pSimpleMath);

二、實作 IDispatch — 支援 VBScript/JScript/晚期繫結

為什麼需要 IDispatch?

  • VB6、VBScript、JScript、ASP 不支援 C++ 指標與早期繫結 (vtbl)。
  • IDispatch::Invoke 以 DISPID、VARIANT 參數陣列實現晚期繫結。
  • 雙重介面 = IUnknown → IDispatch → 自訂介面 (同時支援早期/晚期繫結)。

修改 IDL — 加入 dual、基類 IDispatch、方法 id 屬性

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

Math.h — 宣告 IDispatch 四大方法

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

Math.cpp — Invoke 實作 (核心)

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 (晚期繫結)

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

總結

完成三大里程碑:

  1. 類別工廠:標準化物件建立,支援 CoCreateInstance、CoGetClassObject 快取工廠建立多例。
  2. IDispatch/雙重介面:跨語言、腳本環境可用,早期/晚期繫結雙軌並行。
  3. 完整生命週期:DllCanUnloadNow、LockServer、引用計數正確釋放。

下一章將深入探討:早期 vs 晚期繫結效能、ITypeInfo/ITypeLib 自動化 GetIDsOfNames/Invoke、分散式 COM (DCOM) 基礎。

參考經典

  • Essential COM — Don Box (必讀聖經)
  • COM 技術內幕 — 入門佳作
  • COM 原理實現與應用 — 潘愛明
  • ATL 內幕 — 對應 ATL 開發者
  • MSDN: Dr. GUI 專欄、Platform SDK / Component Services 文件

本文配套完整原始碼 (含 MathCOM DLL、TestMathCOM 客戶端、IDL、Factory) 可供下載。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved