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

從零手寫 COM 組件:逐步實作教學(含 IDL、類別工廠、註冊)

     編輯:關於VC++

前言

真正理解 COM(Component Object Model)的最佳途徑,是**不依賴 ATL/MFC 宏與模板**,純手工用 C++ 實作一個完整的進程內 COM 伺服器。本教學將帶領你從介面定義開始,一步步建構一個「超快加法」COM 物件,並支援 VB 與 VC++ 客戶端呼叫。

第 1 步:定義介面(IDL)

建立 AddObj 專案(Win32 DLL),新增 IAdd.idl:

import "unknwn.idl";

[
    object,
    uuid(1221db62-f3d8-11d4-825d-00104b3646c0),
    helpstring("介面 IAdd:實作超快加法演算法")
]
interface IAdd : IUnknown
{
    HRESULT SetFirstNumber([in] long nX1);
    HRESULT SetSecondNumber([in] long nX2);
    HRESULT DoTheAddition([out, retval] long *pBuffer);
};

[
    uuid(3ff1aab8-f3d8-11d4-825d-00104b3646c0),
    helpstring("CodeGuru 數學演算法介面庫")
]
library CodeGuruMathLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
    interface IAdd;
}

使用 uuidgen.exe 產生 GUID。介面繼承 IUnknown,定義三個方法:設定兩個被加數並回傳和。

第 2 步:編譯 IDL 產生檔案

執行 midl.exe IAdd.idl 產生:

  • IAdd.h:C++ 介面宣告(純虛擬類別)。
  • IAdd_i.c:介面 IID 定義(IID_IAdd、CLSID_AddObject)。
  • IAdd.tlb:二進位類型庫,供 VB/VC++ 客戶端匯入。
  • dlldata.c、IAdd_p.c:Proxy/Stub 程式碼(跨進程/機器調用需用)。

第 3~5 步:實作 COM 物件類別 CAddObj

AddObj.h

#include "IAdd.h"
extern long g_nComObjsInUse;

class CAddObj : public IAdd
{
public:
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IAdd
    STDMETHOD(SetFirstNumber)(long nX1);
    STDMETHOD(SetSecondNumber)(long nX2);
    STDMETHOD(DoTheAddition)(long *pBuffer);

private:
    long m_nX1, m_nX2;
    long m_nRefCount;
};

AddObj.cpp 核心實作

#include <objbase.h>
#include "AddObj.h"
#include "IAdd_i.c"

long g_nComObjsInUse = 0;

// IAdd 方法
STDMETHODIMP CAddObj::SetFirstNumber(long nX1)   { m_nX1 = nX1; return S_OK; }
STDMETHODIMP CAddObj::SetSecondNumber(long nX2)  { m_nX2 = nX2; return S_OK; }
STDMETHODIMP CAddObj::DoTheAddition(long *pBuffer) { *pBuffer = m_nX1 + m_nX2; return S_OK; }

// IUnknown 方法(執行緒安全參考計數)
STDMETHODIMP CAddObj::QueryInterface(REFIID riid, void **ppObj)
{
    if (riid == IID_IUnknown || riid == IID_IAdd) {
        *ppObj = static_cast<IAdd*>(this);
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CAddObj::AddRef()  { return InterlockedIncrement(&m_nRefCount); }
STDMETHODIMP_(ULONG) CAddObj::Release()
{
    long nRef = InterlockedDecrement(&m_nRefCount);
    if (nRef == 0) delete this;
    return nRef;
}

// 建構/解構維護全域物件計數
CAddObj::CAddObj()  : m_nX1(0), m_nX2(0), m_nRefCount(0) { InterlockedIncrement(&g_nComObjsInUse); }
CAddObj::~CAddObj() { InterlockedDecrement(&g_nComObjsInUse); }

第 6~7 步:實作類別工廠 CAddFactory

每個 COM DLL 必須提供 IClassFactory 實作,供 SCM(Service Control Manager)建立物件實例。

// AddObjFactory.h
class CAddFactory : public IClassFactory
{
public:
    STDMETHOD(QueryInterface)(REFIID, void**);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();
    STDMETHOD(CreateInstance)(IUnknown*, REFIID, void**);
    STDMETHOD(LockServer)(BOOL);
private:
    long m_nRefCount;
};

// AddObjFactory.cpp 關鍵實作
STDMETHODIMP CAddFactory::CreateInstance(IUnknown* pUnkOuter, REFIID iid, void** ppv)
{
    if (pUnkOuter) return CLASS_E_NOAGGREGATION; // 不支援聚合
    CAddObj* pObj = new CAddObj;
    if (!pObj) return E_OUTOFMEMORY;
    return pObj->QueryInterface(iid, ppv);
}
STDMETHODIMP CAddFactory::LockServer(BOOL) { return E_NOTIMPL; }
// IUnknown 實作同 CAddObj,操作 m_nRefCount

第 8 步:匯出 DLL 入口函式

建立 Exports.cpp 實作四個標準匯出函式:

STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void** ppv)
{
    if (clsid != CLSID_AddObject) return CLASS_E_CLASSNOTAVAILABLE;
    CAddFactory* pFact = new CAddFactory;
    if (!pFact) return E_OUTOFMEMORY;
    return pFact->QueryInterface(iid, ppv);
}

STDAPI DllCanUnloadNow()
{
    return (g_nComObjsInUse == 0) ? S_OK : S_FALSE;
}

// DllRegisterServer / DllUnregisterServer 寫入/移除登錄機碼
// 詳細實作見原文 Registry.cpp

建立 Exports.def 模組定義檔指定匯出:

EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE

第 9~11 步:專案設定與自動註冊

  • 將 IAdd.idl 加入專案,設定自訂建置步驟:命令列 midl $(InputPath),輸出檔案包含 IAdd.h;IAdd_i.c;IAdd.tlb 等。
  • 專案屬性 → Post-Build Event 加入:regsvr32 /s "$(TargetPath)",每次建置自動註冊。

第 12 步:VB 客戶端測試

Dim iAdd As CodeGuruMathLib.IAdd
Set iAdd = CreateObject("CodeGuru.FastAddition")
iAdd.SetFirstNumber 100
iAdd.SetSecondNumber 200
MsgBox "total = " & iAdd.DoTheAddition()

專案需加入 IAdd.tlb 參考。

第 13~15 步:VC++ 客戶端三種呼叫方式

  1. #import "AddObj.dll"(含內嵌 TLB)→ 智慧指標 IAddPtr 自動管理生命週期。
  2. 直接包含 IAdd.h,手動 CoCreateInstance + QueryInterface。
  3. 使用 MFC ClassWizard 從類型庫產生封裝類別。
// #import 範例
#import "AddObj.dll"
void main()
{
    CoInitialize(NULL);
    CodeGuruMathLib::IAddPtr pAdd;
    pAdd.CreateInstance(L"CodeGuru.FastAddition");
    pAdd->SetFirstNumber(100);
    pAdd->SetSecondNumber(200);
    long result = pAdd->DoTheAddition();
    printf("Result: %d\n", result);
    CoUninitialize();
}

第 14 步:將類型庫嵌入 DLL 資源

資源視圖 → Import 選擇 IAdd.tlb,資源類型 TYPELIB,ID 為 1。此後僅需散佈 AddObj.dll 單一檔案。

總結

本教學完整展示了 COM 物件從 IDL 定義、C++ 實作、類別工廠、DLL 匯出、登錄註冊到客戶端呼叫的全生命週期。理解這些基礎機制,對除錯 ATL/MFC 專案、處理跨語言互通、效能調優都極有幫助。

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