8 る 2015

第 30 卷,第 8 期

本文章是由機器翻譯。

Windows 使用 c + +-與 MIDL 視窗運行時元件

Kenny Kerr |到 2015 2015年 8 月

Kenny Kerr在 2015 年 7 專欄裡 (msdn.microsoft.com/magazine/mt238401),我介紹了 Windows 運行時 (WinRT) 元件的概念作為 COM 程式設計范式的演進。而 Win32 把 COM 放在一邊,Windows 運行時提出了 COM 前面和中心。Windows 運行時是 Win32,後者正在 Windows API 的總稱,它包括多種不同的技術和程式設計模型的繼任者。Windows 運行時提供一致和統一的程式設計模型,但為了使它成功開發商微軟內外需要更好的工具來開發 WinRT 元件和那些從使用元件在應用程式內。

提供由Windows SDK來滿足這一需求的主要工具是 MIDL 編譯器。在 7 月的專欄中,我表明如何 MIDL 編譯器可以生成 Windows 運行時中繼資料 (WINMD) 檔預測需要消耗 WinRT 元件的大多數語言。當然,在 Windows 平臺上任何長期的開發人員將會知道,MIDL 編譯器也會產生一個 C 或 c + + 編譯器可以直接使用的代碼。事實上,MIDL 本身一無所知的 WINMD 檔案格式。它主要是關於解析 IDL 檔和生產代碼用於 C 和 c + + 編譯器,支援 COM 和遠端程序呼叫 (RPC) 開發和生產的代理 Dll。MIDL 編譯器是這樣一塊歷史上重要的機械工程師開發 Windows 運行時選擇不採取破壞它的風險,反而開發"子編譯器"只負責 Windows 運行時。開發人員通常都能不意識的這個花招 — — 並且不需要 — — 但它有助於解釋方式 MIDL 編譯器的工作原理在實踐中。

讓我們看看一些 IDL 的原始程式碼,請參見 MIDL 編譯器到底。這裡是一個定義了一個經典的 COM 介面的 IDL 原始檔案:

C:\Sample>type Sample.idl
import "unknwn.idl";
[uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
interface IHen : IUnknown
{
  HRESULT Cluck();
}

經典的 COM 沒有很強的觀念的命名空間,所以 IHen 介面只定義在檔範圍內。IUnknown 的定義也必須在使用前進口。當然,可以然後只是通過此檔通過 MIDL 編譯器,以便生成的專案數:

C:\Sample>midl Sample.idl
C:\Sample>dir /b
dlldata.c
Sample.h
Sample.idl
Sample_i.c
Sample_p.c

Dlldata.c 原始檔案中包含一個代理 DLL 執行必要的出口的幾個宏。Sample_i.c 包含 IHen 介面的 GUID,您應該使用缺乏支援附加到類型的 Guid uuid __declspec 25 歲編譯器。然後是 Sample_p.c,包含代理 DLL 的封送處理指示。我會掩蓋這些片刻,轉而關注 Sample.h,包含一些非常方便的東西。如果你忽略了所有的可怕的宏,旨在説明 C 開發人員使用 COM (恐怖!) 你會發現這:

MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
IHen : public IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
};

它不是優雅的 c + +,但經過預處理,它相當於從 IUnknown 繼承,並添加自己的純虛函數的 c + + 類。這很方便,因為它意味著你不必用手,寫這篇文章可能引入介面的 c + + 定義與其他工具和語言可能消耗原始 IDL 定義不匹配。這就是本質的 MIDL 編譯器提供 c + + 開發人員,直接生產中一種 c + + 編譯器可以消耗那些的 IDL 原始程式碼翻譯類型。

現在讓我們返回到 Windows 運行時。我會只是略有更新的 IDL 原始程式碼遵守 WinRT 類型更嚴格的要求:

C:\Sample>type Sample.idl
import "inspectable.idl";
namespace Sample
{
  [uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
  [version(1)]
  interface IHen : IInspectable
  {
    HRESULT Cluck();
  }
}

WinRT 介面直接從 IInspectable,必須繼承和部分命名空間用於與執行的元件關聯的類型。如果我嘗試編譯它作為之前,我遇到一個問題:

.\Sample.idl(3) : error MIDL2025 : syntax error : expecting an interface name or DispatchInterfaceName or CoclassName or ModuleName or LibraryName or ContractName or a type specification near "namespace"

MIDL 編譯器不能識別命名空間關鍵字和放棄。/Winrt 命令列選項是用為解決這個問題。它告訴 MIDL 編譯器命令列直接傳到 MIDLRT 編譯器進行預處理的 IDL 原始檔案。它是這第二個編譯器 — — MIDLRT — — 期望我在 7 月的專欄中提到的 /metadata_dir 命令列選項:

C:\Sample>midl /winrt Sample.idl /metadata_dir
  "C:\Program Files (x86)\Windows Kits ..."

作為進一步的證據,仔細看看 MIDL 編譯器輸出,你就會明白我的意思:

C:\Sample>midl /winrt Sample.idl /metadata_dir "..."
Microsoft (R) 32b/64b MIDLRT Compiler Engine Version 8.00.0168
Copyright (c) Microsoft Corporation. All rights reserved.
MIDLRT Processing .\Sample.idl
.
.
.
Microsoft (R) 32b/64b MIDL Compiler Version 8.00.0603
Copyright (c) Microsoft Corporation. All rights reserved.
Processing C:\Users\Kenny\AppData\Local\Temp\Sample.idl-34587aaa
.
.
.

我已經刪除某些依賴項的處理以突出的關鍵點。調用 MIDL 盲目與 /winrt 選項可執行傳遞命令列到 MIDLRT 的可執行檔在退出之前。MIDLRT 解析 IDL 首先要生成 WINMD 檔,但它也產生了另一個臨時的 IDL 檔。這臨時的 IDL 檔是與所有 WinRT 特定的關鍵字,如命名空間,取代原 MIDL 編譯器會接受它的一種原始的翻譯。MIDLRT 然後調用 MIDL 的可執行檔,但如果不使用 /winrt 選項又與臨時的 IDL 檔的位置所以它可以生成的 C 和 c + + 標頭檔和原始檔案作為前的原創組。

刪除原始的 IDL 檔中的命名空間和 IHen 介面的名稱裝飾在臨時的 IDL 檔中,如下所示:

interface __x_Sample_CIHen : IInspectable
.
.
.

這實際上是編碼的形式的解釋由 MIDL 編譯器給出了 MIDLRT 使用調用 MIDL 與預處理輸出時的 /gen_namespace 命令列選項的類型名稱。原始的 MIDL 編譯器然後可以處理這直接沒有 Windows 運行時的特定知識。這只是一個例子,但它給你的這款新工具如何使現有技術的最的想法,要把工作做好。如果你是好奇,想看看這是如何工作的你可能會閒逛 MIDL 編譯器的痕跡,只是發現那些 files—Sample.idl 34587aaa 在前面的示例中的暫存檔案夾 — — 缺掉的。MIDLRT 可執行檔是小心清理後本身,但如果你包括 /savePP 命令列選項,MIDL 不會刪除這些臨時的預處理器檔。不管怎麼說,更多一點預處理中引發和由此產生的 Sample.h 現在有一些東西是即使 c + + 編譯器會識別為命名空間:

namespace Sample {
  MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
  IHen : public IInspectable
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
  };
}

我然後可以實現此介面作為之前,自信,編譯器會來接我的實現與我在 IDL 中編碼的原始定義之間的任何差異。另一方面,如果你只需要 MIDL 生成 WINMD 檔,並且不需要所有的原始檔案為 C 或 c + + 編譯器,你可以避免所有額外的生成工件使用 /nomidl 命令列選項。此選項是由 MIDL 到 MIDLRT 可執行檔可執行檔與所有其他的一起傳遞。MIDLRT 然後跳過它完成生產 WINMD 檔後再次調用 MIDL 的最後一步。這也是習慣,當使用 Windows 運行時 ABI 由 MIDL,包括 /ns_prefix 命令列選項,以便產生的類型和命名空間括"ABI"命名空間,如下所示:

namespace ABI {
  namespace Sample {
    MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
    IHen : public IInspectable
    {
    public:
      virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
    };
  }
}

最後,我應該提及 MIDL 和 MIDLRT 都不是足以產生一個自包含的 WINMD 檔,充分描述的元件的類型。如果你碰巧引用外部類型,通常由作業系統,WINMD 檔,由到目前為止,所描述的程序定義其他類型必須仍然合併與你的目標的 Windows 版本中的主要元資料檔案。讓我來說明這個問題。

我先描述 IHen 介面和一個啟動的母雞類,實現此介面,如中所示的 IDL 命名空間圖 1

圖 1 中 IDL 的母雞類

namespace Sample
{
  [uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
  [version(1)]
  interface IHen : IInspectable
  {
    HRESULT Cluck();
  }
  [version(1)]
  [activatable(1)]
  runtimeclass Hen
  {
    [default] interface IHen;
  }
}

然後,我會實現它使用相同的技術,我在 7 月的專欄中,描述了,只現在我可以依賴定義的 IHen 作為由 MIDL 編譯器提供。現在,裡面一個 WinRT 應用程式,我可以簡單地創建一個母雞物件並調用咯咯方法。我將使用 C# 來說明 app 側的方程:

public void SetWindow(CoreWindow window)   
{
  Sample.IHen hen = new Sample.Hen();
  hen.Cluck();
}

SetWindow 方法是由 C# 應用程式提供的 IFrameworkView 實現的一部分。(我在我 2013 年 8 月列,您可以在閱讀中描述 IFrameworkView msdn.microsoft.com/magazine/jj883951.)而且,當然,這工作。C# 是完全依賴于描述元件的 WINMD 中繼資料。另一方面,它當然會產生一陣微風與 C# 用戶端共用本機 c + + 代碼。大多數情況下,無論如何。出現的一個問題是如果你引用外部類型,當我提及剛才。讓我們更新咯咯方法需要 CoreWindow 作為參數。CoreWindow 是由作業系統定義的所以我不能簡單地定義它裡面我 IDL 原始檔案。

首先,我會更新 IDL 採取依賴核心­視窗介面。我會只導入定義,如下所示:

import "windows.ui.core.idl";

然後會將 ICoreWindow 參數添加成咯咯方法:

HRESULT Cluck([in] Windows.UI.Core.ICoreWindow * window);

MIDL 編譯器會將此"導入"到 #include 的 windows.ui.core.h 頭內部它生成所以我需要做的就是更新我母雞類實現轉:

virtual HRESULT __stdcall Cluck(ABI::Windows::UI::Core::ICoreWindow *) 
  noexcept override
{
  return S_OK;
}

我現在可以編譯之前作為元件和船舶它給應用程式開發人員。C# 應用程式開發人員盡職盡責地咯咯方法調用使用更新對應用程式的 CoreWindow 的引用,如下所示:

public void SetWindow(CoreWindow window)
{
  Sample.IHen hen = new Sample.Hen();
  hen.Cluck(window);
}

不幸的是,C# 編譯器現在抱怨:

error CS0012: The type 'ICoreWindow' is defined in an assembly
  that is not referenced.

你看,C# 編譯器不能識別人相同的介面。C# 編譯器不滿意只是匹配的類型名稱,不能與相同的名稱的視窗類型的連接。不像 c + +,C# 是非常依賴二進位類型資訊來連接點。為了解決這個問題,我可以採用由Windows SDK將撰寫或合併從正確解析 ICoreWindow 到主要的元資料檔案的作業系統 Windows 作業系統以及我的元件的中繼資料,中繼資料提供的另一個工具。這種被稱為 MDMERGE:

c:\Sample>mdmerge /i . /o output /partial /metadata_dir "..."

MIDLRT 和 MDMERGE 可執行檔都相當講究其命令列參數。你需要讓他們的權利只是為了使它的工作。在這種情況下,我只是不能通過指向 /i (輸入) 和 (輸出) /o 選項到同一個資料夾中,因為 MDMERGE 實際上刪除輸入的 WINMD 檔完成後更新到位 Sample.winmd。/ 部分選項告訴 MDMERGE 尋找 /metadata_dir 選項提供的中繼資料中未解決的 ICoreWindow 介面。這就被所謂的引用中繼資料。因此可以用 MDMERGE 來合併多個 WINMD 檔,但在這種情況下,我只用它來解決作業系統類型的參考檔。

在這一點上,由此產生的 Sample.winmd 正確指向中繼資料從 Windows 作業系統時指的 ICoreWindow 介面和 C# 編譯器是滿意和將編譯該應用程式,如寫。加入我下個月繼續 Windows 運行時後從 c + +。


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

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