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

_com_ptr_t 設計解碼:為 VB/Delphi 風格統一而生的 VC 專屬智能指標

     編輯:關於C語言

條款 5:了解 _com_ptr_t 設計背後的歷史緣由

_com_ptr_t 是微軟 VC 專屬模板類,封裝 IUnknown 的 QueryInterface、AddRef、Release,提供成員函數操作 COM 介面指標,簡化引用計數與介面查詢操作。

使用入門:宏特化與類指標語法

需先用 _COM_SMARTPTR_TYPEDEF 宏宣告特化版本,之後用「介面名 + Ptr」定義智能指標:

_COM_SMARTPTR_TYPEDEF(ICalculator, __uuidof(ICalculator));
_COM_SMARTPTR_TYPEDEF(ICOMDebugger, __uuidof(ICOMDebugger));

HRESULT Calculator() {
    ICOMDebuggerPtr spDebugger = NULL;
    ICalculatorPtr spCalculator(CLSID_CALCULATOR); // 建構函數可建立 COM 組件
    int nSum = 0;
    spCalculator->Add(1, 2, &nSum);
    spDebugger = spCalculator; // 自動 QueryInterface 查詢所需介面
    spDebugger->GetRefCount();
    return S_OK;
} // 無需手動 Release,析構時自動釋放

宏通常置於獨立頭文件,include 即可使用類指標語法,感覺不到模板存在。

宏展開揭秘:特化機制

typedef _com_ptr_t<_com_IIID<IMyInterface, __uuidof(IMyInterface)>> IMyInterfacePtr;

_com_IIID 封裝 IID 與型別並綁定,_com_ptr_t 再將其作為型別參數實參,構造特化智能指標型別。

使用 __uuidof 需在介面宣告加 __declspec(uuid("...")):

interface __declspec(uuid("994D80AC-A5B1-430a-A3E9-2533100B87CE")) ICalculator : IUnknown {
    virtual HRESULT STDMETHODCALLTYPE Add(const int nNum1, const int nNum2, int* pnSum) const = 0;
    virtual HRESULT STDMETHODCALLTYPE Sub(const int nMinuend, const int nSubtrahend, int* pnQuotient) const = 0;
};

_com_ptr_t vs CComPtr:設計哲學的根本分歧

_com_ptr_t 封裝更多功能(建構時建立組件、賦值運算子自動查詢介面)。為何 CComPtr 不提供?這涉及設計原則取捨,詳見「在設計原則中斟酌取捨」。

類型庫與 #import:跨語言 COM 開發的關鍵

若拿不到 C/C++ 頭文件卻要調用 COM 組件怎麼辦?這在大量 COM 組件中普遍存在。設計者非不照顧 C++ 開發者,而是採用更好的介面宣告方式——類型庫:語言無關、適合解釋性/巨集語言的 C++ 頭文件等價物。

VC 提供 #import 預處理命令,將類型庫轉為等價 C/C++ 頭文件。以 ADO 為例:

#import "C:\Program Files\Common Files\System\ado\msado15.dll" rename("EOF","rsEOF")

編譯後產出 msado15.tlh(介面宣告 + _COM_SMARTPTR_TYPEDEF 智能指標宣告)與 msado15.tli(介面實作包裝)。註解大寫「DO NOT EDIT!」警告勿修改。

關鍵發現:#import 自動從類型庫生成 _com_ptr_t 智能指標程式碼!這種將預處理命令與特定功能程式碼綁定的行為極少見,#import 自然不可移植,COM 組件本身也無法移植他平台。

VB 風格統一:_com_ptr_t 存在的深層意圖

觀察 VB 操作 COM 程式碼:

dim objVar as MyClass
set objVar = new MyOtherClass
objVar.DoSomething

VB 中 MyClass 與 MyOtherClass 無需關係,只要組件實現了 MyClass 型別介面即正常工作;不支援則拋出執行時異常。將 objVar 視為 _com_ptr_t 實例,用 C++ 重現:


_COM_SMARTPTR_TYPEDEF(MyClass, __uuidof(MyClass));
_COM_SMARTPTR_TYPEDEF(MyOtherClass, __uuidof(MyOtherClass));
MyClassPtr spMyClass = NULL;                // dim objVar as MyClass
MyOtherClassPtr spMyOtherClass(CLSID_MYOTHERCLASS);
spMyClass = spMyOtherClass;                 // set objVar = new MyOtherClass
spMyClass.DoSomething();                    // objVar.DoSomething

驚人相似!_com_ptr_t 透過賦值運算子重載實現不同型別介面查詢,失敗拋出執行時異常——與 VB 一致。

VC 缺乏 COM 必要執行時,_com_ptr_t 設計者可能基於三點考量:

  1. VB 能相容的,VC 也要能用 → #import 導入類型庫;
  2. VB 的介面查詢/使用方式,VC 也應採用 → 重載賦值運算子查詢介面、多建構函數建立物件;
  3. VB 表現出的特點,VC 應同樣表現 → 查詢失敗拋出異常。

它為了能與 VB/Delphi 以相似語法/機制操作 COM 而存在,故多處違背 C/C++ 約定(如賦值運算子可能拋異常),但換來程式碼更易復用、學習曲線更平緩。

_com_ptr_t 統一了不同語言操作 COM 的方式,設計複雜、功能強大,但也暴露不少問題(如條款 7 的自動查詢風險)。無論如何,知其設計意圖,有助理解其他細節。

—— 作者:liuchang5 的專欄

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