本文章是由機器翻譯。

C++

Visual c + + 2015年給 Windows API 帶來現代 c + +

Kenny Kerr

Visual c + + 2015年是 c + + 團隊巨大努力為 Windows 平臺帶來現代 c + + 的高潮。在過去的幾個版本中,Visual c + + 穩步增加了豐富多采的現代的 c + + 語言和庫功能,一起讓絕對令人驚歎的環境來生成通用的 Windows 應用程式和元件。Visual c + + 2015年建立了顯著的進展,介紹了在那些早期的版本,並提供成熟的編譯器,它支援很多 C + + 11 和 c + + 2015年的一個子集。 你可能會認為有關水準的完整性,但認為這是公平地說,編譯器支援的最重要的語言特點,使現代的 c + +,迎接新時代圖書館發展的視窗。那真的就是關鍵。只要編譯器支援高效、 優雅的圖書館的發展,開發人員可以構建偉大的應用程式和元件和相處。

而不是給你的新功能或 pro 的無聊清單­藝高級別旋風式的能力,我會相反穿越你發展的一些傳統的複雜的代碼,現在是,坦白地說,相當的愉快,要寫,因為 Visual c + + 編譯器的成熟。我要給你看些是內在到 Windows,幾乎每一個重大的當前和未來的 API 的心。

這是有點諷刺的是 c + + 是夠 com 最後現代的說,多年來,一直為很多 Windows API 的基礎,並持續為基礎 Windows 運行時元件物件模型。雖然 COM 無可否認綁 c + + 在其原始的設計方面,借很多 c + + 中的二進位和語義的公約,它從未完全優雅。部分 c + + 不被認為是可攜性不夠,例如 dynamic_cast,不得不支援可擕式解決方案開發難度更大,因此,c + + 實現可以作出回避。有多年來使 COM 更加美味為 c + + 開發人員提供了許多解決方案。C + + /cli CX 語言擴展是也許最雄心勃勃迄今由 Visual c + + 團隊。諷刺的是,這些努力改善標準 c + + 支援已經離開了 C + + 在塵土中的 CX 和真的讓語言擴展冗余。

為了證明這一點,我要向您展示如何在現代 c + + 完全執行 IUnknown 和 IInspectable。沒什麼現代或關於這些兩位美女有吸引力。IUnknown 仍然是突出的 Api (如 DirectX 中央抽象。這些介面 — — 與 IInspectable 源自 IUnknown — — 坐在 Windows 運行時的心。我要向您展示如何實現它們沒有任何語言擴展、 介面表或其他宏 — — 只是高效和優雅 c + + 與富含大量類型資訊可以讓編譯器和開發人員有什麼需要構建的大話題。

面臨的主要挑戰是想出一個方法來描述的 COM 或 Windows 運行時的類打算執行,這樣做是方便開發人員和編譯器可以訪問的方式的介面的清單。具體來說,我需要提供此清單類型,這樣編譯器可以審問,即使枚舉介面。如果可以吵拔掉,可能能讓編譯器生成的 IUnknown 調用方法和 (可選),IInspectable GetIids 方法的代碼。它是這兩種方式的最大挑戰。傳統上,唯一的解決辦法涉及語言擴展,醜陋的宏或大量難以維護的代碼。

這兩個方法實現要求一類擬實現的介面的清單。自然的選擇,用於描述的類型的清單是可變參數範本:

template <typename ... Interfaces>
class __declspec(novtable) Implements : public Interfaces ...
{
};

Novtable __declspec 擴展的屬性保持任何建構函式和析構函數無需初始化 vfptr 在這種抽象的類中,這通常意味著代碼大小顯著減少。實現類範本包括一個範本參數包,從而使它的可變參數範本。參數包是一個接受任意數目的範本參數的範本參數。訣竅在於參數包通常用於允許函數接受任意數目的參數,但在這種情況下我在描述他們的言論會審問純粹在編譯時的範本。介面將永遠不會出現在函數參數清單。

這些參數的一種用途已經是顯而易見的。Param­彼得包擴展後形成的公共基類清單。當然,我還負責實際執行這些虛函數,但在這一點上我可以描述一個具體類,實現任意數量的介面:

class Hen : public Implements<IHen, IHen2>
{
};

因為參數包擴展到指定的基類,這些類清單,它等同于什麼我寫過我自己,,如下所示:

class Hen : public IHen, public IHen2
{
};

構建實現類範本以這種方式的優勢在於我可以現在將執行各種樣板代碼插入到實現類範本,而母雞類的開發人員可以使用這個不顯眼的抽象並很大程度上忽視這一切的背後。

到目前為止還順利吧!現在我會考慮 IUnknown 本身的執行。應該能夠執行完全在實現類範本檔中,編譯器現在有供其使用的資訊的類型。IUnknown 提供一樣是必不可少的 COM 類正如氧氣和水對人類的兩個設施。第一,或許較簡單的兩個是引用計數和是由哪個 COM 物件跟蹤其存留期的手段。COM 規定侵入引用計數藉以每個物件負責管理其自身的存留期基於其認識多少未完成的引用存在一種的形式。這是擁有權的與引用計數等 C + + 11 shared_ptr 類範本,其中的物件具有其共用沒有知識的智慧指標。你可能就兩種方法的利弊爭論,但在實踐 COM 方法通常是更有效率,它是只是 COM 的工作所以你必須處理它的方式。如果沒有別的,你可能會同意這是一個可怕的想法來包裝內 shared_ptr 的 COM 介面 !

我將首先拿唯一的運行時開銷介紹了通過實現類範本:

protected:
  unsigned long m_references = 1;
  Implements() noexcept = default;
  virtual ~Implements() noexcept
  {}

預設建構函式不是真正的開銷的本身 ; 它只是確保生成的建構函式 — — 這將初始化的引用計數 — — 而不是公眾保護。受保護的引用計數和虛擬析構函數。使派生類的引用計數允許更複雜的類組成。大多數類完全可以忽略這一點,但注意到了嗎我初始化一個引用計數。這是對比表明引用計數最初應為零,因為沒有引用已發放尚未流行智慧。這種做法是由 ATL 推廣,無疑受到DonBox 基本 COM,但它是很有問題,作為研究 ATL 原始程式碼也可以證明。首先假定該引用的擁有權立即將由調用方承擔或附加到智慧指標提供遠較少容易出現錯誤的施工過程。

一個虛擬析構函數是極大的便利,它允許實現類範本來實現引用計數,而不是強迫混凝土類本身來提供實現。另一個選擇是使用奇怪的是反復出現的範本模式以避免虛函數。通常我會更喜歡這種做法,但它會稍,複雜抽象和由其本身的性質的 COM 類由於 vtable,那裡是沒有令人信服的理由,以避免一個虛擬的函數。隨著這些原語在的地方,成為一個簡單的問題,在實現類範本內執行 AddRef 和釋放。第一,AddRef 方法可以簡單地使用 InterlockedIncrement 內在撞了引用計數:

virtual unsigned long __stdcall AddRef() noexcept override
{
  return InterlockedIncrement(&m_references);
}

這基本上是不言自明的。Don不被誘惑來了一些複雜的計畫,藉以你可能有條件地替換為 InterlockedIncrement 和 InterlockedDecrement 的內建函式 c + + 增量和遞減運算子。ATL 試圖這樣做的複雜性的巨大代價。如果您關注的是效率,寧願花你的努力避免雜散調用 AddRef 和釋放。再次,現代 c + + 是其支援移動語義和能夠移動的無參考凹凸引用擁有權解圍。現在,釋放法只是稍微更複雜的:

virtual unsigned long __stdcall Release() noexcept override
{
  unsigned long const remaining = InterlockedDecrement(&m_references);
  if (0 == remaining)
  {
    delete this;
  }
  return remaining;
}

引用計數和的結果賦給一個區域變數。這是重要的因為應該返回這個結果,但是如果物件均遭毀壞它然後會非法添加的成員變數的引用。假設有沒有未完成的引用,只是通過對上述虛析構函數的調用刪除的物件。就這樣結束了引用計數,和具體的母雞類是仍然像過去一樣的簡單:

class Hen : public Implements<IHen, IHen2>
{
};

現在是時間考慮調用的精彩世界。實現此 IUnknown 方法是一個非平凡的練習。我廣泛報導這在我的 Pluralsight 課程,你可以讀到許多的奇妙方式由DonBox"基本 COM"(艾迪生-Wesley 專業,1998年) 滾動您自己的實現。被警告雖然這是 COM 上優秀的文本,它基於 C + + 98,並不代表現代 c + + 以任何方式。從空間和時間,我會假設您有一些熟悉執行調用和焦點反而對如何實現與現代 c + +。這裡是虛擬方法本身:

virtual HRESULT __stdcall QueryInterface(
  GUID const & id, void ** object) noexcept override
{
}

鑒於標識某個特定介面的 GUID,調用應確定該物件是否實現所需的介面。如果是這樣,它必須遞增該物件的引用計數,然後通過 out 參數返回所需的介面指標。如果不是,它必須返回一個 null 指標。因此,我將以一個粗略的輪廓開始:

*object = // Find interface somehow
if (nullptr == *object)
{
  return E_NOINTERFACE;
}
static_cast<::IUnknown *>(*object)->AddRef();
return S_OK;

所以調用首先嘗試以某種方式找到所需的介面。如果介面不支援,必要的 E_NO­返回介面錯誤代碼。請注意如何我已經搞定的要求清除失敗結果的介面指標。你應該考慮的調用很多二元運算。它也成功地找到所需的介面或不。Don不會試圖獲得創造性的在這裡,只有條件地積極回應。雖然有一些有限的選項,允許通過 COM 規範,大多數消費者會簡單地臆想的介面不支援,無論什麼故障代碼,你可能會返回。您的實現中的任何錯誤無疑將導致您沒有調試痛苦的盡頭。調用至關重要,擺弄。最後,通過生成的介面指標,再以支援某些罕見但允許類組成情況下調用 AddRef。那些並不顯式支援的實現類的範本,但在這裡而將很好的例子。它是重要的是牢記引用計數操作是特定于介面,而不是特定于物件的。你只是無法在屬於一個物件的任何介面上調用 AddRef 或釋放。你必須尊重有關物件標識的 COM 規則,否則你可能會引入非法將打破以神秘的方式的代碼。

那麼如何發現是否請求的 GUID 表示類打算執行的介面?這是在哪裡我能回到的類型資訊實現類範本收集通過其範本參數包。請記住,我的目標是讓編譯器來實現這對我來說。 我想生成的代碼效率方面要好像我寫了它,用手,或更好。因此,我會做一套可變函數範本,範本函數本身包括範本參數包此查詢。我先踢東西了 BaseQueryInterface 函數範本:

virtual HRESULT __stdcall QueryInterface(
  GUID const & id, void ** object) noexcept override
{
  *object = BaseQueryInterface<Interfaces ...>(id);

BaseQueryInterface 是本質上是一個現代的 c + + 投影的 IUnknown 調用。而不是返回 HRESULT,它直接返回的介面指標。失敗明顯地表示與一個空指標。它接受一個函數參數,標識該介面的 GUID 來找到。更重要的是,我擴展其整個類範本參數包,以便 BaseQueryInterface 函數就可以開始的進程的枚舉介面。你首先會想到那是因為 BaseQueryInterface 是它可以簡單地實現類範本的成員訪問此清單的介面直接,但我需要允許此函數以皮脫落在清單中的第一個介面,如下所示:

template <typename First, typename ... Rest>
void * BaseQueryInterface(GUID const & id) noexcept
{
}

這種方式,BaseQueryInterface 可以標識的第一個介面和離開,其餘部分用於後續的搜索。你看,COM 有大量的具體的規則,以支持對象標識調用必須執行或至少榮譽。尤其是,請求 IUnknown 必須始終返回確切相同的指標,以便用戶端可以確定兩個介面指標是否引用同一個物件。因此,BaseQueryInterface 函數是一個偉大的地方,執行一些這些公理。因此,我可能會開始比較與第一個範本參數,表示該類打算執行的第一個介面請求的 GUID。如果不是一場比賽,我會檢查是否正在請求 IUnknown:

if (id == __uuidof(First) || id == __uuidof(::IUnknown))
{
  return static_cast<First *>(this);
}

我只是假設其中一種一根火柴,返回的第一個介面明確介面的介面指標。Static_cast 確保編譯器不會麻煩本身具有的多個介面基於 IUnknown 多義性。演員只是調整指標在類 vtable 中找到正確的位置,因為所有介面 vtable 都入手 IUnknown 的三種方法,這是非常有效。

雖然我在這裡也可以對於 IInspectable 查詢,以及可選的支援。IInspectable 是一個相當奇怪的怪獸。在某種意義上它是 Windows 運行時的 IUnknown,因為 Windows 運行時的每個介面投射到語言 (如 C# 和 JavaScript 必須直接從 IInspectable,而不是僅僅 IUnknown 孤獨。這是一個不幸的現實,以適應公共語言運行庫實現的物件和介面的方式 — — 這是與 c + + 的方式工作和如何 COM 歷來被定義。當它來到物件組成,但這是一個大的課題,我將在後續文章中,它也有一些相當令人遺憾的性能影響。調用而言我只被需要確保可以查詢 IInspectable,這應執行 Windows 運行時類,而不是只是一個經典的 COM 類。雖然關於 IUnknown 顯式的 COM 規則不適用於 IInspectable,但我可以簡單地對待後者多相同的方式在這裡。但這面臨兩大挑戰。首先,我需要發現是否任何實現的介面從 IInspectable 派生。而且,第二,我需要一個此類介面的類型,這樣我就可以返回一個適當的調整的介面指標沒有含糊之處。如果我可以假設在清單中的第一個介面總是會基於 IInspectable,你可能只需要更新 BaseQueryInterface,如下所示:

if (id == __uuidof(First) ||
  id == __uuidof(::IUnknown) ||
  (std::is_base_of<::IInspectable, First>::value &&
  id == __uuidof(::IInspectable)))
{
  return static_cast<First *>(this);
}

請注意我使用 C + + 11 is_base_of 類型特徵來確定第一個範本參數是否為 IInspectable 派生的介面。這將確保隨後的比較排除由編譯器您應該實施一個經典的 COM 類,且不支援 Windows 運行時。以這種方式我更可以無縫地支援 Windows 運行時和經典的 COM 類,而又沒有任何額外的句法複雜性元件開發人員並沒有任何不必要的運行時開銷。但這樣一個非常微妙的 bug 的潛力如果你碰巧先列出非 IInspectable 介面。我需要做的是 is_base_of 替換可以掃描整個清單上的介面的東西:

template <typename First, typename ... Rest>
constexpr bool IsInspectable() noexcept
{
  return std::is_base_of<::IInspectable, First>::value ||
    IsInspectable<Rest ...>();
}

IsInspectable 仍然依賴于 is_base_of 類型的特徵,但現在將其應用於每個介面直至找到一個匹配。如果找到基於 IInspectable 沒有介面,則命中終止函數:

template <int = 0>
constexpr bool IsInspectable() noexcept
{
  return false;
}

我會回到一會兒有好奇的無名預設參數。假設 IsInspectable 返回 true,我需要找到的第一個基於 IInspectable 的介面:

template <int = 0>
void * FindInspectable() noexcept
{
  return nullptr;
}
template <typename First, typename ... Rest>
void * FindInspectable() noexcept
{
  // Find somehow
}

我可以再次依靠 is_base_of 類型的特徵,但是這一次返回實際的介面指標應該找到匹配:

#pragma warning(push)
#pragma warning(disable:4127) // conditional expression is constant
if (std::is_base_of<::IInspectable, First>::value)
{
  return static_cast<First *>(this);
}
#pragma warning(pop)
return FindInspectable<Rest ...>();

BaseQueryInterface 然後可以簡單地使用 IsInspectable 和 FindInspectable IInspectable 支援的查詢:

if (IsInspectable<Interfaces ...>() && 
  id == __uuidof(::IInspectable))
{
  return FindInspectable<Interfaces ...>();
}

再次,給出了具體的母雞類:

class Hen : public Implements<IHen, IHen2>
{
};

實現類範本將確保編譯器將生成最高效的代碼是否 IHen 或 IHen2,來源從 IInspectable 或簡單地從 IUnknown (或一些其他介面)。現在我終於可以實現調用以支付任何額外的介面例如前面示例中的 IHen2 的遞迴部分。BaseQueryInterface 的結論是通過調用 FindInterface 函數範本:

template <typename First, typename ... Rest>
void * BaseQueryInterface(GUID const & id) noexcept
{
  if (id == __uuidof(First) || id == __uuidof(::IUnknown))
  {
    return static_cast<First *>(this);
  }
  if (IsInspectable<Interfaces ...>() && 
    id == __uuidof(::IInspectable))
  {
    return FindInspectable<Interfaces ...>();
  }
  return FindInterface<Rest ...>(id);
}

請注意我要求在許多地方這 FindInterface 函數範本相同的方式,我最初稱為 BaseQueryInterface。在這種情況下,我通過它其餘的介面。具體來說,,我擴展參數包,就這樣,它可以再次識別清單的其餘部分中的第一個介面。但這是一個問題。因為範本參數包不擴大作為函數參數,我可能最終會在哪裡的語言不會讓我表示,我真的想人煩惱的情況。但更多的在那一瞬間。這個"遞迴"FindInterface 可變參數範本是正如你可能期望:

template <typename First, typename ... Rest>
void * FindInterface(GUID const & id) noexcept
{
  if (id == __uuidof(First))
  {
    return static_cast<First *>(this);
  }
  return FindInterface<Rest ...>(id);
}

它第一個範本參數的其餘部分隔開,返回調整的介面指標 (如果存在匹配項。否則,它調用本身直到用盡的介面清單。編譯時遞迴,我們鬆散做這稱為,固然重要,請注意,此函數範本 — — 和實現類範本中的其他類似的例子 — — 並非技術上遞迴的即使在編譯時。每個具現化函數範本調用不同的具現化函數範本。例如,FindInterface <IHen、 IHen2> 調用 FindInterface <IHen2> 調用 FindInterface <>。為了使它是遞迴的 FindInterface <IHen、 IHen2> 將需要調用 FindInterface <IHen、 IHen2>,它沒有。

然而,請記住,此"遞迴"發生在編譯時,因為如果你寫了所有這些如果發言的手,一個接一個。但現在觸礁。這個序列是如何終止的?當範本參數清單是空的當然。問題是 c + + 已經定義為空範本參數清單中意味著什麼:

template <>
void * FindInterface(GUID const &) noexcept
{
  return nullptr;
}

這是幾乎是正確的但編譯器會告訴你一個函數範本不適合這種專業化。而且,然而,如果不提供此終止函數,編譯器將無法編譯的最後調用時參數包是空的。這不是一例為函數重載,因為參數清單保持不變。幸運的是,解決方案是很簡單的。我可以避免看起來像一家專業化提供無名預設實參的終止函數:

template <int = 0>
void * FindInterface(GUID const &) noexcept
{
  return nullptr;
}

編譯器是幸福的和如果請求不受支援的介面,則此終止函數只返回一個 null 指標和虛擬調用方法將返回 E_NOINTERFACE 錯誤代碼。而且,照顧 IUnknown。如果你關心的就是經典的 COM,則可以安全地停止存在,這就是所有你所需要。值得重申在此時編譯器會優化此調用實現,與各項"遞迴"函式呼叫和常量運算式,這樣的代碼是您至少一樣好可以手工編寫。並對 IInspectable 可以達到相同的目標。

對於 Windows 運行時類,執行 IInspectable 增加了複雜性。此介面不是幾乎和提供設施相比絕對重要職能的 IUnknown 可疑集合,IUnknown 作為根本。儘管如此,我會離開討論這一篇文章,在這裡集中高效和現代的 c + + 實現,以支援任何 Windows 運行時類。首先,我會得到 GetRuntimeClassName 和 GetTrustLevel 的虛函數的方式。這兩種方法相對來說很小,實施,也很少使用,這樣可以很大程度上可以掩蓋它們的實現。GetRuntimeClassName 方法應返回 Windows 運行時字串表示物件的運行時類的完整名稱。我會離開取決於類本身去執行應該它決定這樣做。實現類範本可以簡單的返回 E_NOTIMPL 示意不實現此方法:

HRESULT __stdcall GetRuntimeClassName(HSTRING * name) noexcept
{
  *name = nullptr;
  return E_NOTIMPL;
}

同樣,GetTrustLevel 方法只是返回一個枚舉的常量:

HRESULT __stdcall GetTrustLevel(TrustLevel * trustLevel) noexcept
{
  *trustLevel = BaseTrust;
  return S_OK;
}

請注意我不顯式標記這些 IInspectable 方法作為虛擬函數。避免虛擬宣言允許編譯器中,除去這些方法,應 COM 類實際上並沒有實現任何 IInspectable 介面。現在我將把我的注意力轉向 IInspectable GetIids 方法。這是比調用甚至更容易錯誤。雖然其執行不是幾乎為關鍵,編譯器生成的有效執行是可取的。GetIids 返回一個動態分配的陣列的 Guid。每個 GUID 表示物件看來是執行的一個介面。你首先會的想到這是只什麼物件通過調用,支援的聲明,但這是正確只有面值。GetIids 方法可能會決定保留一些從發佈的介面。不管怎麼說,我會開始用其基本定義:

HRESULT __stdcall GetIids(unsigned long * count, 
  GUID ** array) noexcept
{
  *count = 0;
  *array = nullptr;

第一個參數指向一個調用方提供的變數,GetIids 方法必須設置為所得陣列中的介面的數量。第二個參數指向一個陣列的 Guid 和是如何執行傳達動態分配的陣列返回給調用者。在這裡,我已經開始通過清除這兩個參數,只是為了安全起見。我現在需要確定有多少介面的類實現。我很樂意說只需使用 sizeof 運算子,可以提供一個參數包的大小,如下所示:

unsigned const size = sizeof ... (Interfaces);

這是非常方便,編譯器是高興地報告也會出現,如果此參數包擴展的範本參數的數目。這實際上也是生產在編譯時已知的一個值的常量運算式。這不會做,正如我剛才提到的原因是因為它是非常常見的 GetIids 拒絕他們不願和大家一起分享一些介面的實現。這樣的介面被稱為披著斗篷的介面。任何人都可以查詢他們通過調用,但 GetIids 不會告訴你他們是可用。因此,我需要為排除披著斗篷的介面,可變 sizeof 運算子提供編譯時更換,我需要提供一些方法來聲明和識別這種披著斗篷的介面。我將開始與後者。我想要盡可能為元件開發人員實現類,所以較不顯眼的機制是為了方便。只是,我可以提供已掩蔽類範本,來"裝飾"任何披著斗篷的介面:

template <typename Interface>
struct Cloaked : Interface {};

然後我可以決定在向所有的消費者都不知道具體的母雞類上實現特殊的"IHenNative"介面:

class Hen : public Implements<IHen, IHen2, Cloaked<IHenNative>>
{
};

因為已掩蔽類範本來自其範本參數,現有的調用執行繼續無縫地工作。我在這裡補充一點我現在可以再一次在編譯時,查詢的額外的類型資訊。為此我將定義 IsCloaked 類型特徵,所以我可以方便地查詢以確定是否它已經被掩蔽的任何介面:

template <typename Interface>
struct IsCloaked : std::false_type {};
template <typename Interface>
struct IsCloaked<Cloaked<Interface>> : std::true_type {};

我現在可以數未掩蔽的介面,再次使用遞迴可變參數函數範本:

template <typename First, typename ... Rest>
constexpr unsigned CounInterfaces() noexcept
{
  return !IsCloaked<First>::value + CounInterfaces<Rest ...>();
}

並且,當然,我需要一個終止的函數,它可以簡單的返回零:

template <int = 0>
constexpr unsigned CounInterfaces() noexcept
{
  return 0;
}

與現代 c + + 編譯時做這種算數運算的能力是驚人的強大而又驚人地簡單。我現在可以繼續充實 GetIids 執行通過請求此計數:

unsigned const localCount = CounInterfaces<Interfaces ...>();

一個皺紋是常量運算式編譯器的支援還不很成熟。雖然這無疑是一個常數運算式,編譯器不但是榮譽 constexpr 成員函數。理想情況下,我可以將 CountInterfaces 函數範本標記為 constexpr 和所生成的運算式將同樣是一個常數的運算式,但是編譯器不還看到它那種方式。另一方面,毫無疑問編譯器會優化此代碼無論如何沒有麻煩了現在,如果不管出於什麼原因 CounInterfaces 發現沒有未掩蔽的介面,GetIids,可以簡單地返回成功因為生成的陣列將為空:

if (0 == localCount)
{
  return S_OK;
}

又在此基礎上,這實際上是一個常數運算式,則編譯器將生成的代碼沒有條件的一種方式或另一種。換句話說,如果不有任何未掩蔽的介面,則剩餘的代碼只是從執行進行刪除。否則,執行被被迫分配一個適當大小的陣列的使用傳統的 COM 分配的 Guid:

GUID * localArray = static_cast<GUID *>(CoTaskMemAlloc(sizeof(GUID) * localCount));

當然,這可能會失敗,在這種情況下,我只是返回相應的 HRESULT:

if (nullptr == localArray)
{
  return E_OUTOFMEMORY;
}

在這一點上,GetIids 已經準備好使用 Guid 來填充的陣列。正如你可能期望,我需要最後一次枚舉介面將複製到此陣列的每個未掩蔽的介面的 GUID。已經做之前,我將使用一對函數範本:

template <int = 0>
void CopyInterfaces(GUID *) noexcept {}
template <typename First, typename ... Rest>
void CopyInterfaces(GUID * ids) noexcept
{
}

可變參數範本 (第二個函數) 只可以使用 IsCloaked 類型特徵來確定是否要複製介面遞增指標之前由其第一個範本參數標識的 GUID。以這種方式,該陣列遍歷時無需跟蹤的多少個元素可能包含或者到哪裡應該寫的陣列中。我也抑制有關此常量的運算式的警告:

#pragma warning(push)
#pragma warning(disable:4127) // Conditional expression is constant
if (!IsCloaked<First>::value)
{
  *ids++ = __uuidof(First);
}
#pragma warning(pop)
CopyInterfaces<Rest ...>(ids);

正如你所看到的"遞迴"致電到 CopyInterfaces 最終用途可能遞增指標類型的值。快了。然後,GetIids 執行可以通過調用 CopyInterfaces 來填充陣列返回給調用者之前得出結論:

CopyInterfaces<Interfaces ...>(localArray);
  *count = localCount;
  *array = localArray;
  return S_OK;
}

同時,具體的母雞類是無視編譯器以它的名義做的所有工作:

class Hen : public Implements<IHen, IHen2, Cloaked<IHenNative>>
{
};

而這正是因為它應該與任何好的庫。Visual c + + 2015年編譯器在 Windows 平臺上提供對標準 c + + 令人難以置信的支援。它使 c + + 開發人員能夠生成精美優雅而又高效的庫。這支援 Windows 運行時元件在標準 c + + 的發展,以及他們從普遍的 Windows 應用程式完全用標準 c + + 編寫的消費。實現類範本是從現代 c + + 的 Windows 運行時只是一個例子 (見 moderncpp.com)。


Kenny Kerr 是一個位於加拿大,以及作者 Pluralsight 和微軟最有價值球員的電腦程式員。他的博客 kennykerr.ca ,你可以跟著他在 Twitter 上 twitter.com/kennykerr

感謝以下的微軟技術專家對本文的審閱:JamesMcNellis