小提示
現代 QueryInterface 模式——使用 IID_PPV_ARGS 與智慧指標。 原始 QueryInterface 呼叫容易出錯(IID 與指標類型不匹配)。 現代 C++ 程式碼應使用具型別安全性的輔助工具:
#include <wrl/client.h> // Microsoft::WRL::ComPtr
Microsoft::WRL::ComPtr<IUnknown> unknown = /* ... */;
Microsoft::WRL::ComPtr<IPersistFile> persistFile;
// ✅ Best — ComPtr::As() handles QI + type safety + Release automatically
HRESULT hr = unknown.As(&persistFile);
// ✅ Good — IID_PPV_ARGS macro ensures IID matches the pointer type
hr = unknown->QueryInterface(IID_PPV_ARGS(&persistFile));
// ❌ Dangerous — IID and pointer type can mismatch silently
hr = unknown->QueryInterface(IID_IPersistFile, (void**)&persistFile);
C++/WinRT 等價物:
#include <winrt/base.h>
winrt::com_ptr<IUnknown> unknown = /* ... */;
auto persistFile = unknown.as<IPersistFile>(); // throws on failure
auto maybePF = unknown.try_as<IPersistFile>(); // returns nullptr on failure
關鍵規則: 切勿在沒有 QueryInterface 的情況下轉型介面指標——COM 識別規則要求每個介面指標都必須透過 QI 或 CoCreateInstance 取得。 直接鑄造(static_cast, reinterpret_cast)則產生未定義的行為。
當你擁有物件介面的初始指標後,COM 有一個非常簡單的機制來判斷該物件是否支援另一個特定介面,若支援,則取得指向該介面的指標。 (關於如何取得物件介面的初始指標,請參見「取得指向物件的指標」。)此機制即為 IUnknown 介面的 QueryInterface 方法。 如果物件支援所請求的介面,該方法必須回傳指向該介面的指標。 這讓物件能夠自由地在其所支援的介面中移動。 QueryInterface 將「您是否支持某個合約?」與談判成功後該合約的高效能使用區分開來。
當用戶端首次取得物件存取權時,該用戶端至少會收到一個 IUnknown 介面指標(最基本的介面),透過它來控制物件的壽命——透過告訴物件何時完成使用該物件——並呼叫 QueryInterface。 用戶端被程式設計要求每個物件執行某些操作,但 IUnknown 介面沒有這些操作的函式。 相反地,這些操作會透過其他介面來表達。 因此,客戶端被設計為可針對這些介面與物件進行協商。 具體來說,客戶端會呼叫 QueryInterface ,請求物件提供介面,讓客戶端能透過此介面呼叫所需的操作。
由於物件實作 了 QueryInterface,因此它有能力接受或拒絕請求。 如果物件接受客戶端的請求, QueryInterface 會回傳一個新的指標指向所請求的介面給客戶端。 透過該介面指標,客戶端可存取該介面的方法。 反之,若物件拒絕客戶端請求, QueryInterface 會回傳一個空指標(錯誤),客戶端無法透過指標呼叫所需函式。 在這種情況下,客戶必須優雅地面對這種可能性。 例如,假設一個客戶端有一個指向物件介面 A 的指標,並要求介面 B 和 C。假設該物件支援介面 B,但不支援介面 C。結果是物件回傳指向 B 的指標,並回報 C 不支援。
一個關鍵點是,當物件拒絕對 QueryInterface 的呼叫時,用戶端無法要求物件執行透過所請求介面表達的操作。 用戶端必須有一個介面指標,才能在該介面中呼叫方法。 如果該物件拒絕提供所要求的指標,客戶端就必須準備好在沒有該指標的情況下處理:要麼不去做原本打算用該物件進行的操作,要麼改用另一個或許功能較不強大的介面。 與其他物件導向系統相比,COM功能的這項特性表現得更好,因為在這些系統中,你無法在呼叫該函式之前知道該函式是否能運作,即使如此,失敗處理仍是不確定性的。 QueryInterface 提供一種可靠且一致的方式,讓你在嘗試呼叫其方法前,知道物件是否支援該介面。
QueryInterface 方法也提供了一種穩健且可靠的方式,讓物件表示不支援特定契約。 也就是說,如果在呼叫 QueryInterface 時,詢問一個「舊」物件是否支援「新」介面(例如,舊物件出貨後才被發明的介面),舊物件會可靠且不會造成當機,回答「否」。支持此機制的技術是分配 IID 的演算法。 雖然這看似小事,但對系統整體架構極為重要,而能夠查詢舊有元素新增功能的能力,令人驚訝的是,這是大多數其他物件架構中不具備的功能。
相關主題