2015 年 9 月

第 30 卷,第 9 期

本文章是由機器翻譯。

Windows 與 C++ - Windows 執行階段中的一些優質類型

Kenny Kerr | 2015 年 9 月

Kenny KerrWindows 執行階段 (WinRT) 可讓元件開發人員對應用程式開發人員呈現 classy 型別系統。假設完全使用 COM 介面實作 Windows 執行階段時,這看起來似乎不合理的開發人員熟悉 c + + 和傳統 com 使用。COM 畢竟是集中在介面而非類別的程式設計模型。COM 啟用模型可讓類別建構 CoCreateInstance 與朋友,但這攻擊支援只是近似於預設建構函式。WinRT RoActivateInstance 卻無法阻擋變更這樣的觀感。那麼要如何運作?

它不是實際為 incongruous 可能一開始出現。COM 啟用模型已提供支援 classy 類型系統所需的基本抽象概念。它只是缺少必要描述至取用者的中繼資料。COM 啟用模型定義類別物件和執行個體。某些類別的執行個體永遠不會建立直接由取用者。即使應用程式開發人員呼叫來建立類別的執行個體的 CoCreateInstance,OS 還是必須取得類別物件以擷取所需的執行個體。

Windows 執行階段呼叫類別物件的新名稱但機制相同。啟用處理站是取用者的第一個連接埠的呼叫至元件。而不需要執行 DllGetClassObject,元件會匯出函式呼叫 DllGetActivationFactory。類別所識別的方式已變更,但結果只是一個物件查詢可能會進一步與指定的類別。下文呼叫 [啟用原廠傳統類別物件可能已實作 IClassFactory 介面可讓呼叫者建立該類別的執行個體。在新環境中啟用 Windows 執行階段中的處理站必須實作新的 IActivationFactory 介面 — 有效地提供相同的服務。

我探討了在我的專欄最後兩個期,在 Windows 執行階段中使用可啟動屬性裝飾的執行階段類別會產生指示語言推測允許預設建構的中繼資料。假設是屬性的類別啟用處理站會提供透過 IActivationFactory 的 ActivateInstance 方法的這類的預設建構物件。以下是簡單 runtimeclass IDL 表示具有預設建構函式的類別中:

[version(1)]
[activatable(1)]
runtimeclass Hen
{
  [default] interface IHen;
}

其他建構函式也可能在 IDL 中使用您已經猜到了,介面外罩集合表示使用不同的參數集的其他建構函式方法的描述。就像 IActivationFactory 介面會定義預設建構、 元件和類別的特定介面可能會描述參數化的建構。以下是簡單的 factory 介面可用於建立特定數字的 clucks 時物件:

runtimeclass Hen;
[version(1)]
[uuid(4fa3a693-6284-4359-802c-5c05afa6e65d)]
interface IHenFactory : IInspectable
{
  HRESULT CreateHenWithClucks([in] int clucks,
                              [out,retval] Hen ** hen);
}

像 IActivationFactory,IHenFactory 介面必須直接繼承自 IInspectable 無明顯原因。每個此 factory 介面的方法在使用指定的參數的其他建構函式為規劃和每個必須以此邏輯型別為類別的傳回值。此處理站介面就明確指出介面名稱的另一個可啟動屬性透過執行階段類別相關聯:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
runtimeclass Hen
{
  [default] interface IHen;
}

如果預設建構函式沒有什麼道理類別只要省略原始的啟動屬性和類別的語言推測同樣缺乏預設建構函式。如果我寄送我家禽元件的版本並接著了解它缺乏能夠建立具有大型 combs hens 嗎? 我當然無法變更 IHenFactory 介面既然我已經在因為 COM 介面是一定是不可變的二進位和語意合約送給客戶。沒問題,我可以只定義容納任何其他建構函式的另一個介面:

[version(2)]
[uuid(9fc40b45-784b-4961-bc6b-0f5802a4a86d)]
interface IHenFactory2 : IInspectable
{
  HRESULT CreateHenWithLargeComb([in] float width,
                                 [in] float height,
                                 [out, retval] Hen ** hen);
}

然後我可以將這個第二個 factory 介面關聯時類別做為之前:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
[activatable(IHenFactory2, 2)]
runtimeclass Hen
{
  [default] interface IHen;
}

語言推測負責合併和應用程式開發人員只要看到數建構函式多載示 [圖 1

在 C# 中的 IDL 定義的 Factory 方法
[圖 1 在 C# 中的 IDL 定義的 Factory 方法

元件開發人員必須小心來實作這些處理站介面,以及必要條件 IActivationFactory 介面:

struct HenFactory : Implements<IActivationFactory,
                               ABI::Sample::IHenFactory,
                               ABI::Sample::IHenFactory2>
{
};

這裡一次我要仰賴在 2014 年 12 月專欄中所述實作類別樣板 (msdn.microsoft.com/magazine/dn879357)。即使我選擇禁止預設建構,時啟用 factory 仍然必須實作 IActivationFactory 介面因為元件必須實作 DllGetActivationFactory 函式是硬式編碼傳回 IActivationFactory 介面。這表示語言投影必須先呼叫 QueryInterface 上產生的指標如果應用程式需要非預設建構的任何項目。仍然,IActivationFactory 的 ActivateInstance 虛擬函式的實作是必要的但這類不執行作業就夠了:

virtual HRESULT __stdcall ActivateInstance(IInspectable ** instance) noexcept override
{
  *instance = nullptr;
  return E_NOTIMPL;
}

其他建構方法可以實作中的方式最適合特定的實作,但是簡單的解決方法是直接轉送至內部類別本身的引數。如下所示:

virtual HRESULT __stdcall CreateHenWithClucks(int clucks,
                                              ABI::Sample::IHen ** hen) noexcept override
{
  *hen = new (std::nothrow) Hen(clucks);
  return *hen ? S_OK : E_OUTOFMEMORY;
}

這是假設 c + + 時類別沒有擲回的建構函式。我可能需要配置 clucks 該建構函式中的 c + + 向量。在此情況下,簡單的例外狀況處理常式就夠了:

try
{
  *hen = new Hen(clucks);
  return S_OK;
}
catch (std::bad_alloc const &)
{
  *hen = nullptr;
  return E_OUTOFMEMORY;
}

靜態類別成員呢? 您不應該會很驚訝這也以實作啟用處理站上的 COM 介面。傳回在 IDL,我可以定義來容納所有的靜態成員的介面。如何去報告中後院層 hens 數目:

[version(1)]
[uuid(60086441-fcbb-4c42-b775-88832cb19954)]
interface IHenStatics : IInspectable
{
  [propget] HRESULT Layers([out, retval] int * count);
}

我要將此介面與我相同的執行階段類別以靜態屬性產生關聯時:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
[activatable(IHenFactory2, 2)]
[static(IHenStatics, 1)]
runtimeclass Hen
{
  [default] interface IHen;
}

C + + 中的實作也是相當直接。我會以下列方式更新實作 variadic 範本:

struct HenFactory : Implements<IActivationFactory,
                               ABI::Sample::IHenFactory,
                               ABI::Sample::IHenFactory2,
                               ABI::Sample::IHenStatics>
{
};

並提供 get_Layers 的合適實作虛擬函式:

virtual HRESULT __stdcall get_Layers(int * count) noexcept override
{
  *count = 123;
  return S_OK;
}

我還是讓您提供更精確的標頭計數...我的意思時計數。

對取用端,內部應用程式中所有出現非常簡單且精簡。中所示 [圖 1, ,IntelliSense 經驗是很有用。我可以使用 CreateHenWithClucks 建構函式就好像在 C# 類別只是另一個建構函式:

Sample.Hen hen = new Sample.Hen(123); // Clucks

當然,C# 編譯器和 Common Language Runtime (CLR) 具有相當的工作順序執行。在執行階段,CLR 會呼叫 RoGetActivationFactory,呼叫 LoadLibrary 後面 DllGetActivationFactory 擷取 Sample.Hen 啟用處理站。它接著會呼叫 QueryInterface 來擷取 factory 的 IHenFactory 介面實作,和它要才呼叫 CreateHenWithClucks 虛擬函式來建立新版物件。這是更別提許多,許多其他呼叫 QueryInterface CLR 堅持在每個 prods 它並進入 CLR 來探索各種屬性和語意 (semantics) 的每個物件所說的物件。

並呼叫靜態成員會出現同樣相當簡單:

int layers = Sample.Hen.Layers;

但這裡再次 CLR 必須呼叫 RoGetActivationFactory 來取得啟動的處理站,後面接著 QueryInterface 來擷取 IHenStatics 介面指標才能呼叫 get_Layers 虛擬函式可擷取這個靜態屬性的值。因此成本有點分成許多這類呼叫 CLR,不過,快取啟用處理站。相反地,它也使得處理站快取而毫無意義且不必要地複雜元件內的各種嘗試。但這一天的主題。請跟我下個月我們會繼續從 c + + 探索 Windows 執行階段。


Kenny Kerr 是電腦程式設計人員在加拿大和作者基礎 Pluralsight 和 Microsoft MVP。他的部落格網址 kennykerr.ca 而您可以依照他在 Twitter 上 twitter.com/kennykerr


感謝以下的微軟技術專家對本文的審閱: 拉裡 · 奧斯特曼