WinRTはCOMである ── IInspectable・.winmd・言語プロジェクション、そしてWinUIが今もバイナリ契約の上に乗っている理由

· 更新日: · · Windows, WinRT, COM, WinUI, Windows App SDK, Windows開発

更新履歴(1件・最終更新 2026年09月05日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

技術的な主張と注意事項を維持し、導入・見出し・比較表を整理して説明の順序をわかりやすくした。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.22170942)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「WinRTはCOMである ── IInspectable・.winmd・言語プロジェクション、そしてWinUIが今もバイナリ契約の上に乗っている理由」合同会社小村ソフト. https://comcomponent.com/blog/winrt-is-com/

DOI(登録済みアーカイブ)
10.5281/zenodo.22170942
DOI(前回登録した版)
10.5281/zenodo.22170943

WPFやWinFormsのアプリにWindowsの新しい機能を足したい。ところが、WinRTのピッカーを呼ぶと例外になる。WinUIの話を読むと、今度はUIを作り直す必要があるようにも見える。こうした戸惑いは、WinRTの仕組みと、UIフレームワークの選択を分けると整理できます。

この記事の出発点は、WinRTもCOMのバイナリ契約の上にあるということです。Microsoft自身も「The Windows Runtime is based on COM(Windows RuntimeはCOMに基づく)」と明記しています。1 前回のOLEオブジェクトの記事で見たWordへのExcel表の埋め込みも、今のWinRTやWinUIも、根には同じ IUnknown があります。

ここでは、まずWinRTを構成する3つの要素を押さえ、次にデスクトップアプリから使う際の注意点、最後に既存資産をどう扱うかを考えます。対象読者はCOMまたはWindowsデスクトップ開発の経験がある開発者、前提環境はWindows 10/11と.NET 6以降(C#)またはC++17(C++/WinRT)、難易度は中級です。

1. まず結論

押さえたい結論は3つです。

  • WinRTはマネージドランタイムではなく、COMを土台にしたABI(バイナリ契約)です。その契約に、型情報を伝える .winmd と、各言語から自然に呼ぶための言語プロジェクションが加わっています。1234
  • WinRT APIの大半は、既存のWPF・WinForms・Win32アプリから使えます。ただし、HWNDの受け渡し、package identity、スレッド初期化という3つの前提を確認する必要があります。5678
  • WinUIへのUI全面移行と、WinRT APIの部分利用は別の判断です。WinUIもWinRT ABIの上にあり、既存のCOM/ActiveX資産とWinRTは同じ土台で共存できます。921

以降は、次の順に読むとつながりを追いやすくなります。

知りたいこと 読む章
なぜ「WinRTはCOM」と言えるのか 2〜3章: COMとの共通点と IInspectable
なぜC#やC++から自然に呼べるのか 4〜5章: .winmd と言語プロジェクション
既存アプリから使うには何を確認するのか 6章: HWND・package identity・アパートメント
WinUIへ移行すべきなのか 7〜9章: WinUIの足元、移行判断、通知の登録条件

以下の知識マップは、各要素の関係を見返すためのものです。まず説明を読みたい場合は、2章から順に進んでください。

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. OLEもWinRTも、根は同じバイナリ契約

当ブログではこれまで、COMの設計思想STA/MTAのスレッドモデルActiveX/OCXの扱いOLE複合ドキュメントと、古典COMの世界を追いかけてきました。これらはすべて1990年代からの技術です。

一方、WinRTはWindows 8(2012年)で導入されたAPI基盤です。現在は Windows.* 名前空間のAPI群としてトースト通知・共有・Bluetooth・OCRなどを提供し、WinUIとWindows App SDKの足元にもなっています。10 新旧まったく別の世界に見えますが、実体は地続きです。

共通するのは、インターフェースを通じて呼ぶという約束

COMコンポーネントとWinRTクラスは、どちらもインターフェースを通じて機能を公開します。土台となるインターフェースを比べると、関係は次のようになります。1

古典COM WinRT
インターフェースの基底は IUnknown インターフェースの基底は IInspectable。その基底が IUnknown

つまり、WinRTはCOMと無関係な仕組みに置き換えたのではなく、IUnknown の上に一段積み増したものです。参照カウントも QueryInterfaceHRESULT も、そのまま生きています。

C++/WinRTのドキュメントも、WinRT APIを「COMの進化(an evolution of COM)」と呼び、COMに基づくAPIを言語プロジェクションを通じて使う設計だと説明しています。211

古典COMとWinRTの系譜IUnknownとvtableによるCOMのバイナリ契約という共通の土台の上に、1990年代からのOLE・ActiveXなど古典COMの世界と、2012年以降のWinRTとその上のWinUI・Windows App SDKの世界が並んで建っており、両者は排他ではなく地続きであるCOMのバイナリ契約(IUnknown・vtable)古典COM(OLE・ActiveX・自作COM)WinRT(IInspectable・.winmd)WinUI/Windows App SDK同じ土台の上で共存できる

図1: 古典COMとWinRTは別世界ではなく、同じバイナリ契約の上の新旧2世代である。

だからこの記事は、単なる「新しいAPIの紹介」ではありません。すでに持っているCOMの知識が、2026年のWindows開発のどこに効くのかを確かめる記事です。

3. IUnknownの上のIInspectable

変わらない土台と、追加された3メソッド

WinRTの型システムの公式仕様では、すべてのWinRTインターフェースは暗黙に IInspectable を要求し、IInspectableIUnknown を要求すると定めています。IUnknown が定義するのは、従来どおり QueryInterfaceAddRefRelease の3メソッドです。12

その上に、IInspectable が次の3メソッドを追加します。13

メソッド 役割
GetIids このオブジェクトが実装するインターフェースのIID一覧を返す
GetRuntimeClassName 完全修飾されたWinRT型名(Windows.Storage.StorageFile など)をHSTRINGで返す
GetTrustLevel オブジェクトの信頼レベルを返す
IUnknownの上に乗るIInspectableすべてのWinRTインターフェースはIInspectableを要求し、IInspectableはIUnknownを要求する。IUnknownがQueryInterface・AddRef・Releaseを、IInspectableがGetIids・GetRuntimeClassName・GetTrustLevelを提供し、その上に各WinRTインターフェースのメソッドが乗るIUnknown(QI・AddRef・Release)IInspectable(GetIids・型名・信頼レベル)各WinRTインターフェースのメソッド

図2: WinRTのオブジェクトはIUnknownの3メソッドの上にIInspectableの3メソッドを積み、その上に個々のAPIが乗る。

型名から、メソッド・プロパティ・イベントの定義を引ける

重要なのは、追加されたメソッドの数よりも、型名とメタデータをつなげられることです。

古典COMでは、実行時にオブジェクトの素性を知る標準の方法は「IIDを知っていて QueryInterface で聞く」ことでした。スクリプト言語向けには IDispatch という別経路がありました。

WinRTでは、GetRuntimeClassName で得た型名を、次章で扱うメタデータ .winmd で解決できます。そこからメソッド・プロパティ・イベントの完全な定義を得られます。仕様自身が、メタデータで解決可能なWinRT型名を取得できることが「言語プロジェクションを可能にする(enables language projection)」と述べています。12

GetRuntimeClassNameから言語プロジェクションへ呼び出し側がオブジェクトのGetRuntimeClassNameを呼ぶと完全修飾のWinRT型名が返り、その型名をWindows Metadataで解決すると型の完全な定義が得られ、これが各言語への投影を可能にするWinRTオブジェクト型名(GetRuntimeClassName).winmdで型定義を解決言語プロジェクションが可能に

図3: 「実行時に型名が取れて、型名からメタデータが引ける」ことがWinRTの仕掛けの中心にある。

ユーザー定義インターフェースの「継承」と「要求」は分ける

COMに慣れた人が注意したい違いもあります。WinRTの型システムには、ユーザー定義インターフェース同士の継承がありません。古典COMの IFileSystemBindData2 : IFileSystemBindData のような派生は意図的に持たず、代わりに「インターフェースAはインターフェースBを要求する(requires)」という宣言で表現します。121

これは、ここまで見た IUnknownIInspectable という基底のABIチェーンとは別の話です。この基底はすべてのWinRTインターフェースの土台として残っています。

ユーザー定義の契約はvtableの継承レイアウトに依存しない、より疎な形に寄せた。その一方で、呼び出しの実体は今もvtable経由です。契約の書き方の違いと、呼び出しの仕組みを混同しないことが大切です。

4. .winmdが解決したこと ── 型情報のバインディング地獄

古典COMの難しさは「型情報の配り方」にあった

古典COMの実務では、インターフェースの実装そのものより、型情報をどう配るかに手間がかかりました。

利用する側 型情報を届ける経路
C++ IDLで契約を書き、MIDLでヘッダーとプロキシ/スタブを生成する
VB6・スクリプト 型ライブラリ(TLB)を配る
.NET 相互運用アセンブリを別途作る

TLBにはオートメーション寄りの型の制約があり、IDLに書けてもTLBに入らない情報があります。さらに、言語ごとに経路が分かれているため、どれか一つが古くなると型の不一致につながります。この苦労は型ライブラリとdscomの記事DLL・COMインターフェースの後方互換性の記事で扱ったとおりです。

.winmdは、全言語が読む共通の契約書

WinRTの答えが Windows Metadata(.winmd) です。APIを機械可読のメタデータとして記述し、ツールと言語プロジェクションがそれを読んで、各言語向けの投影を生成します。3

Windowsはシステム提供の全WinRT APIのメタデータを同梱し、実行時に名前空間や型を解決するAPIも提供します。Windows SDKにはコンパイル時用のコピーが入っています。サードパーティも、自分のWinRTコンポーネントに .winmd を付ければ、システムAPIと同じ仕組みで言語プロジェクションに参加できます。3

ここで変わったのは、配布される型情報の形です。言語ごとにばらけていたTLB・ヘッダー・相互運用アセンブリの代わりに、単一の .winmd を全言語のプロジェクションが読むようになりました。

ただし、IDLが不要になったわけではありません。WinRTコンポーネントを作るときは、今もIDL(WinRT向けに刷新されたMIDL 3.0)で契約を記述し、MIDLコンパイラが .winmd を生成します。14

WinRTコンポーネントの制作パイプラインWinRTコンポーネントの契約は今もIDLすなわちMIDL 3.0で記述し、MIDLコンパイラがそれを.winmdにコンパイルする。配布されるのはこの.winmdで、cppwinrt.exeやcswinrt.exeなど各言語のプロジェクションがこれを読んで投影を生成する。置き換わったのはIDLではなく配布される型情報の形である契約を記述(IDL・MIDL 3.0)MIDLコンパイラ.winmd(配布される型情報)各言語の投影を生成

図4: 契約の入り口(IDL)は現役のまま、配布される型情報の出口が.winmdに統一された。

.NETと同じファイル形式でも、マネージドランタイムではない

.winmd の物理形式は、CLRアセンブリと同じ ECMA-335仕様を使います。ただし、有効なデータの組み合わせの規則はCLRアセンブリとは異なります。形式を借りたことと、実行にCLRが必要なことは別です。3

ここは、システムAPIとサードパーティのコンポーネントを分けて読む必要があります。

対象 .winmdと実装の関係
システム提供のWinRT API .winmd は実行コードを含まない純粋なメタデータ。実装はOSのネイティブDLLにあり、実行にCLRは不要
サードパーティのWinRTコンポーネント .winmd が実装コードを含む場合もある。マネージドな(C#で書かれた)コンポーネントでMSILを含む場合、実行には対応する.NETランタイムが必要

.winmd をツールで開くと.NETアセンブリのように見えるため、「WinRT=マネージド」と誤解しがちです。しかし、システム提供の .winmd の中身は、COMインターフェースの契約書です。3

.winmdと実装の分離.winmdはECMA-335の物理形式を借りたメタデータで、システム提供のものは実行コードを含まない契約書であり、システム提供のWinRT APIの実装はOSのネイティブDLLにある。この分離のため.winmdが.NETアセンブリのように見えてもシステムWinRT APIの実行にCLRは不要である(サードパーティのマネージドなコンポーネントの.winmdはMSILを含み.NETランタイムを要する)システム提供はコード無し型定義と実装が対応.winmd(契約書・ECMA-335形式)OSのネイティブDLL(実装)システムAPIはCLR不要

図5: システム提供のWinRT APIでは.winmdは契約書、実装はOSのネイティブDLL。形式が.NET風でも実行はネイティブのCOM。

古典COMの型情報と.winmdの対比古典COMではIDLからC++ヘッダー、型ライブラリからVB6やスクリプト、相互運用アセンブリから.NETと、言語ごとに型情報の経路が分かれてばらつきの原因になっていたのに対し、WinRTでは単一の.winmdを全言語のプロジェクションが共通に読む古典COM:経路が言語ごとIDL→C++ヘッダーTLB→VB6・スクリプト相互運用アセンブリ→.NET.winmd(単一のメタデータ)全言語のプロジェクションが共通に読む

図6: 言語ごとにばらけていた型情報の配り方を、.winmdは「1つのメタデータを全員が読む」形に畳み直した。

制約が消えたのではなく、投影しやすさを軸に変わった

古典COMを知っている人向けに一言でまとめると、.winmd は「型ライブラリのやり直し」です。TLBが果たそうとした役割を、ECMA-335という実績ある形式の上で、最初から全言語共通の正本として設計し直したものと捉えると、位置づけがすっきりします。

ただし、制約が無くなったわけではありません。TLBのオートメーション寄りの制約が、「全言語へ安全に投影できること」を軸にしたWinRT独自の型システムの制約に置き換わったのです。3章で見た、ユーザー定義インターフェース同士の継承がないことも、その一例です。12

したがって、既存のCOM/IDL契約をそのままWinRTへ持ち込めるとは限りません。APIの設計し直しが必要になる場合があります。

型ライブラリの制約からWinRT型システムの制約への置き換えTLB(型ライブラリ)が持っていたオートメーション寄りの表現力の制約は、.winmdで取り払われたのではなく、全言語へ安全に投影できることを軸にしたWinRT独自の型システムの制約に置き換わった。その一例がユーザー定義インターフェース継承の不在で、既存のCOM/IDL契約をそのまま持ち込めるとは限らず、APIの設計し直しが必要になる場合がある置き換えTLBの制約(オートメーション寄り)WinRT型システムの制約(投影可能性軸)例:ユーザー定義の継承は不可既存COM契約は設計見直しの場合あり

図7: TLBの制約は「消えた」のではなく、全言語への投影可能性を軸にした別の制約に置き換わった。

5. C++/WinRT・C#/WinRTは「ラッパー」ではなく投影

共通の契約を、各言語に自然な形で見せる

.winmd という共通の契約書があると、各言語での見せ方をツールで自動生成できます。これが言語プロジェクション(language projection)です。WinRT APIを各言語の慣用に沿って公開し、COMの詳細を隠すことで、その言語にとって自然なプログラミング体験を提供します。411

Microsoftが現在サポートする投影は、次の2つです。4

投影 生成するもの 特徴
C++/WinRT cppwinrt.exe.winmd からC++向けの投影ヘッダーを生成 標準C++17のヘッダーファイルベースの投影。C++/CXのような言語拡張は不要で、C++/CXとWRLの後継11
C#/WinRT(CsWinRT) cswinrt.exe.winmd からC#コードを生成し、相互運用アセンブリにする .NET向けの投影。ランタイムから独立したツールチェーン1516

C#側の経緯は少し紛らわしいので、切り分けておきます。.NET Core 3.xまでは、WinRT/winmdの利用を.NETランタイムが組み込みでサポートしていました。.NET 5でその組み込みサポートが削除され、C#/WinRTへ移りました。16 WinRT APIが使えなくなったのではなく、投影を担当する場所が変わったのです。

いまC#で net8.0-windows10.0.19041.0 のようなTFMを指定すると、Windows SDKの投影アセンブリが自動参照されます。10

.winmdから各言語への投影の生成単一の.winmdをcppwinrt.exeが読むとC++17向けの投影ヘッダーが、cswinrt.exeが読むとC#向けの相互運用アセンブリが生成され、それぞれの言語の慣用に沿った形でWinRT APIが公開される.winmd(APIの契約書)cppwinrt.exe→C++17ヘッダーcswinrt.exe→C#相互運用アセンブリC++の慣用で呼べるC#の慣用で呼べる

図8: 投影は手書きのラッパーではなく、契約書(.winmd)からツールが機械的に生成する。

自動生成されても、呼び出しの実体はCOMのまま

この記事で「ラッパー」ではなく「投影」と呼ぶのは、人がAPIごとに追従する翻訳層ではなく、メタデータから機械的に導出する仕組みであることを強調するためです。.winmd に載っているAPIを、最初から全対応言語で使える形にできます。

そして、どの言語から呼んでも、投影の下で起きていることは同じCOM呼び出しです。たとえばC#で await picker.PickSingleFolderAsync() と書くと、投影はWinRTの IAsyncOperation を.NETの Task の世界へ橋渡しします。それでも、ABI側ではvtable経由のメソッド呼び出しと HRESULT が使われています。エラーがCOM例外(0x80070005 のようなHRESULT)の形で現れるのは、このためです。

C#コードからOSのWinRT APIまでの層アプリのC#やC++のコードは言語プロジェクションを通じてWinRTのABIすなわちIInspectableのvtable呼び出しに変換され、OSが実装するWinRT APIに届く。投影がCOMの詳細を隠しているだけで、呼び出しの実体はCOMであるアプリのコード(C#・C++)言語プロジェクションWinRT ABI(IInspectableのvtable)OSのWinRT API実装

図9: 投影が隠しているのはCOMの「詳細」であって、COMそのものではない。

この構造を知っていると、トラブルを2つの層に分けられます。生成コードのバージョン不一致やTFMの設定漏れなら投影の層、HRESULT・アパートメント・参照カウントならABIの層です。後者では、COM開発の経験がそのまま武器になります。

6. デスクトップアプリで詰まる点 ── HWND・identity・アパートメント

まず「APIを参照できること」と「動作条件」を分ける

WinRT APIの大半は、WPF・WinForms・Win32のデスクトップアプリから呼べます。5 呼び出すための入口は、C#とC++で次のように異なります。10

環境 最初の設定
C#/.NET 6以降 TargetFrameworknet8.0-windows10.0.19041.0 のようなWindows OSバージョン付きTFMにする
C++ Microsoft.Windows.CppWinRT NuGetパッケージを導入し、C++17以降でC++/WinRTを使う

ただし、参照できたからといって、すべてのAPIがそのまま動くわけではありません。次の3点を確認します。トースト通知の登録条件は9章で別に整理します。

確認すること 主な対処
表示先のウィンドウを必要とするか UIに対応したCOM interopでHWNDを渡す
package identityが必要か MSIXまたは外部の場所を指すパッケージでidentityを付与する
スレッドがWinRT用に初期化されているか ネイティブコードではSTA/MTAを指定して初期化する。C#では通常ランタイム側が処理する

詰まりどころ1: ピッカーなどのUIにはHWNDを渡す

一部のピッカーやダイアログ、共有UIは、UWPの CoreWindow を表示先として想定しています。デスクトップアプリには CoreWindow がないため、表示前にオーナーウィンドウのHWNDを明示的に渡す必要があります。6

ピッカーなどで使う入口が、IInitializeWithWindow というCOMインターフェースです。これは IUnknown を継承し、デスクトップアプリで使うWinRTオブジェクトにオーナーウィンドウを提供します。17

C#では、まず使用中のUIフレームワークに応じてHWNDを取得します。186

オーナーウィンドウ HWNDの取得方法
WinUIの Window WinRT.Interop.WindowNative.GetWindowHandle
WPFのウィンドウ WindowInteropHelper
WinFormsのフォーム フォームの Handle プロパティ

次に、WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd) でピッカーに渡し、その後で表示します。C++/WinRTなら、オブジェクトを as<IInitializeWithWindow>() で取得してから Initialize(hwnd) を呼びます。初期化を省くと、例外になるか、黙って失敗します1819

モダンなWinRTオブジェクトに、古典COMのインターフェースを QueryInterface して初期化する。この橋渡しが公式の作法になっているところにも、WinRTがCOMであることが表れています。

共有UIは別のインターフェースです。DataTransferManager に対しては IInitializeWithWindow を使いません。専用の IDataTransferManagerInterop を使い、ShowShareUIForWindow にHWNDを渡します。6

新しいWindows App SDKのピッカーも別経路です。Microsoft.Windows.Storage.Pickers はコンストラクタで WindowId を受け取るため、InitializeWithWindow のパターンは不要です。ただし、TFMの設定だけで使えるAPIではありません。Windows App SDKの導入に加え、unpackagedアプリでは配布先へのランタイムの配備・初期化が前提です。19

デスクトップアプリからピッカーを表示する手順デスクトップアプリがピッカーを生成した後、そのままPickSingleFolderAsyncを呼ぶと例外や沈黙の失敗になるため、先にオーナーウィンドウのHWNDを取得しIInitializeWithWindowのInitializeで渡してから表示する必要があるピッカー(WinRT)デスクトップアプリピッカー(WinRT)デスクトップアプリalt[HWNDを渡さず表示][先にHWNDを渡す]生成PickSingleFolderAsync例外または沈黙の失敗IInitializeWithWindowでHWNDを設定PickSingleFolderAsyncピッカーが表示される

図10: 「デスクトップにCoreWindowはない」ことを、HWNDの明示的な受け渡しで埋めるのが公式の作法。

詰まりどころ2: package identityが必要なAPIがある

トースト通知の履歴(ToastNotificationHistory)、ジャンプリスト、共有ターゲットなど、一部のWinRT APIはpackage identityを持つアプリ(パッケージ化されたアプリ)でしか動きません。従来型のインストーラーで配るunpackagedなアプリから呼ぶと失敗します。7

対処は2つです。MSIXでパッケージ化するか、既存インストーラーを保ったまま識別子を付与する「外部の場所を指すパッケージ(いわゆるsparse package)」を使います。20 どのAPIがidentityを要求するかは、公式の一覧で確認できます。7

package identityが必要なAPIへの2つの経路package identity必須のWinRT APIをunpackagedなアプリから呼ぶと失敗するため、MSIXでパッケージ化するか、既存インストーラーを維持したまま識別子だけを付与する外部の場所を指すパッケージのどちらかでpackage identityを持たせるidentity必須のAPIを使いたいMSIXでパッケージ化外部の場所を指すパッケージpackage identityを獲得通知履歴などが動く

図11: identity必須APIへの対処は「MSIX」か「既存インストーラー+識別子付与」の二択。

詰まりどころ3: スレッド初期化とSTA/MTAを確認する

WinRTオブジェクトを扱うスレッドは、事前にWinRT用の初期化が必要です。ネイティブコードでは RoInitializewinrt::init_apartment を使い、STA/MTAという並行性モデルを指定します。C#のWPF/WinFormsアプリでは、通常ランタイム側が初期化を処理します。8

RoInitialize は、COMの CoInitializeEx と同じ枠組みの、WinRT世代の入口です。CoInitialize のドキュメント自身も、Windows Runtimeを使う場合は代わりに RoInitialize または Windows::Foundation::Initialize を呼ぶよう案内しています。21

WPF/WinFormsのUIスレッドはSTAであること、UIオブジェクトはUIスレッドで触ること、ブロッキング待機がSTAでデッドロックを招くこと。STA/MTAの記事で扱った考え方は、WinRT APIでもそのまま生きています。

HWNDやidentityを用意しても使えないAPIはある

ここまでの対処は、デスクトップ利用のための入口があるAPIの話です。CoreWindowApplicationView そのものに依存するAPIは、デスクトップアプリでは使えません。この場合は、初期化を足すのではなく代替APIを探します。57

デスクトップからWinRT APIを呼ぶときの詰まりどころの分岐まずスレッドがWinRT用に初期化されていることを確認し(ネイティブコードではRoInitializeなどでSTA/MTAを明示的に指定する。C#では通常ランタイム側が処理する)、そのうえで、呼びたいWinRT APIがCoreWindow前提のUI系ならIInitializeWithWindowでHWNDを渡すか新ピッカーを使い、package identity必須ならMSIXまたは外部の場所を指すパッケージで識別子を付与し、CoreWindowやApplicationView自体に依存するAPIはデスクトップでは使えないため代替APIを探し、それ以外の大半のAPIはTFMやC++/WinRTの設定だけでそのまま呼べるUI系identity必須CoreWindow自体に依存それ以外スレッド初期化呼びたいAPIの性質は?ネイティブは明示指定、C#は通常自動HWNDか新ピッカーMSIXか識別子付与代替APIを探すそのまま呼べる

図12: スレッド初期化(ネイティブコードでは明示的に、C#では通常ランタイム任せ)を前提として、詰まりどころは3系統に分類でき、それぞれ定型の対処がある。

COMとWinRTのスレッド初期化の対応古典COMではCoInitializeExでSTAまたはMTAを指定してスレッドを初期化するのに対し、WinRTではRoInitializeで同じくSTAまたはMTAの並行性モデルを指定して初期化する。CoInitializeのドキュメントもWinRT利用時はRoInitializeを呼ぶよう案内しており、アパートメントの考え方は共通である古典COM:CoInitializeExアパートメント(STA/MTA)WinRT:RoInitializeUIスレッドはSTA・待機に注意

図13: 初期化APIの名前は変わっても、アパートメントという概念は同じものが使われ続けている。

7. WinUIの足元 ── 公開開発になっても契約は変わらない

公開開発への移行は、バイナリ契約の変更ではない

MicrosoftはWinUIの本流開発をGitHubの公開の場へ移す方針を、2025年夏に段階的アプローチとして正式にアナウンスしました。ミラーの更新頻度を上げ、ローカルビルドを可能にし、テストを整えたうえでコミュニティからの貢献を受け付け、最終的にGitHubを開発の主本拠にする、という4段階です。22

本記事の公開時点(2026年8月末)では、公式ドキュメントも「WinUIは公開の場で開発されている(built in the open)」と明記しています。公開リポジトリで日々のエンジニアリングの進捗を追えるようになっています。9

WinForms→WPF→UWP→WinUIと世代交代を追いかけてきた開発者が、「またフレームワークが変わるのか」と感じるのは自然です。しかし、変わってきたUIフレームワークと、その下の土台は分けて見る必要があります。Win32とCOM、そして2012年以降のWinRT ABIは、同じ場所にあり続けました。

WinUIのウィンドウも、HWNDに裏打ちされている

WinUIはWindows App SDKの一部として提供されるWinRTのAPI群です。923 その Microsoft.UI.Xaml.Window は、UWP世代の CoreWindow ベースのウィンドウモデルを置き換える、HWNDに裏打ちされたウィンドウです。公式の相互運用チュートリアルも、まずウィンドウハンドルを取得するところから始まります。24

つまりWinUIアプリは、HWNDのウィンドウの上で、IInspectableの契約に従うオブジェクトツリーが動いているWin32アプリです。COMで身につけたQueryInterface・参照カウント・アパートメントの知識も、Win32で身につけたHWNDとメッセージループの知識も、WinUIのトラブルシューティングでそのまま通用します。

UIフレームワークの世代交代と変わらない土台WinForms・WPF・UWPのXAML・WinUIとUIフレームワークは世代交代を重ねてきたが、WinFormsとWPFはWin32とCOMの土台に直接乗り、UWPのXAMLとWinUIはWinRT ABIを介して同じWin32とCOMの土台に乗る。世代交代してきたのは上の層で、足元の契約は変わっていないUIフレームワークの世代交代WinForms・WPFUWPのXAMLWinUI(現在)WinRT ABI(2012年〜)変わらない土台(Win32+COM)

図14: 浮き沈みしてきたのはフレームワークの層。WinForms・WPFはWin32+COMに直接、UWP・WinUIはWinRT ABIを介して、同じ土台に乗っている。

WinUIアプリを支える層WinUIのXAMLとコントロールはWindows App SDKの一部として提供され、WinRTのABIすなわちIInspectableの契約の上で動き、さらにその下はCOMとWin32のHWNDという土台である。UIフレームワークの世代交代の下で、この足元の契約は変わっていないWinUI(XAML・コントロール)Windows App SDKWinRT ABI(IInspectable)COM+Win32(HWND)

図15: WinUIの下にはWinRT ABIがあり、さらにその下は古典COMとWin32である。積み方が変わっただけで土台は同じ。

XAML Islandsによる部分混在は、世代ごとの制約を確認する

「既存のWPF/WinForms画面に、WinUIコントロールだけを混ぜる」という戦略は、XAML Islandsの世代を分けて評価する必要があります

世代 WPF/WinFormsから使う際の状況
UWP世代のXAML Islands Windows Community Toolkitのラッパーコントロールがある。ただしWPF/WinForms向けは.NET Core 3.x世代までで、現行の.NETではサポートされない25
WinUI 3世代 Windows App SDKの DesktopWindowXamlSource でWPF・WinForms・Win32からホストできる。ただしUWP世代のような便利なラッパーコントロールはなく、ホスティングAPIを直接扱う実装・検証の負担がある26

段階混在を計画するなら、コントロール単位の混在だけを前提にしないほうが安全です。機能単位のWinRT API利用(6章)や、画面・プロセス単位の分離を軸に考えるほうが、2026年時点では堅実です。

8. 業務アプリへの含意 ── 全面移行と部分利用は別問題

「WinRTは新しい別世界だから、使うには既存資産を捨てて作り直すしかない」。受託の現場で出会うこの誤解は、2つに分けると解けます。

既存のCOM資産とWinRTは共存できる

Excel COMオートメーションを使い、ActiveXコントロールを載せ、自社COMコンポーネントを呼んでいるWPFアプリに、WinRT APIを足すことはできます。特別な曲芸ではなく、同じCOM基盤の上で機能を組み合わせているからです。

たとえばトースト通知なら、TFMを設定してAPIを参照し、通知を出すための登録条件を満たします。後者を忘れないため、9章で経路別に整理します。C++/WinRTでは、WinRTと古典COMの両方のインターフェースを winrt::com_ptrwinrt::implements という同じ仕組みで扱えます。21

UIの刷新と、機能の追加は別々に見積もる

「UIをWinUIへ全面移行するか」と「WinRT APIを必要な箇所だけ使うか」は、規模も期間もリスクも異なる判断です。

UI全面移行はフレームワーク選定の問題で、画面資産・サードパーティコントロール・開発体制に依存します。この判断はWinForms/WPF/WinUIの選び方で扱いました。一方、WinRT APIの部分利用は、既存アプリに今日から取り組める小さな改善です。

トースト通知が欲しいだけの案件で、UI全面移行を見積もる必要はありません。逆に、「WinUIに移行しないから」という理由でWinRT APIの利用まで見送る必要もありません。

全面移行と部分利用は別の判断WinUIへのUI全面移行はフレームワーク選定の大きな判断で画面資産や体制に依存するのに対し、WinRT APIの部分利用はTFM設定などで既存のWPFやWinFormsアプリに今日から足せる小さな判断であり、この2つを混同せずに分けて検討する「新しいWindowsの機能を使いたい」UI全面移行(大きな判断)WinRT API部分利用(小さな判断)画面資産・体制に依存既存アプリに今日から足せる

図16: 「新しい機能」への道は2本あり、混同すると見積もりも判断も狂う。

9. 判断表 ── 残す・包む・置き換えるのWinRT版

場面ごとに、必要な変更だけを選ぶ

ActiveXの判断表の延長として、WinRT側の場面別判断をまとめます。

場面 推奨 理由
WPF/WinFormsからトースト・共有・BluetoothなどWinRT APIを使いたい TFM設定(またはC++/WinRT導入)で部分利用 UI移行なしで今日から呼べる。ただし共有UIは IDataTransferManagerInterop 経由(6章)、トーストは表の下の補足を参照106
ピッカー・ダイアログで例外/沈黙の失敗 IInitializeWithWindow でHWNDを渡す。新規はWindowId対応の新ピッカー(Windows App SDKの導入が前提) CoreWindow前提の設計をHWNDで埋める公式の作法619
通知履歴・ジャンプリストなどが動かない MSIXまたは外部の場所を指すパッケージでpackage identityを付与 identity必須APIはパッケージ化が前提720
既存のCOM/ActiveX/OLE資産 捨てない。残す/包む/置き換えは資産ごとに判断 WinRTと排他ではなく同じ土台で共存できる(8章)
新規デスクトップアプリのUI WinUIを第一候補として評価(WPFも現役) 公開開発で投資方向が明確化。足元はWinRT ABI229
既存WPF/WinForms画面へWinUIコントロールを部分混在 ラッパー不在の実装負担を見込んで慎重に評価 UWP世代Islandsは.NET Core 3.x止まり、WinUI 3世代はホスティングAPIのみ2526
「WinRTはUWPと一緒に終わった」という前提の計画 前提を修正する WinRTはデスクトップから呼べる現行のAPI基盤5

トースト通知は、TFMの設定だけでは表示されない

APIを呼べるようにする設定と、通知を表示するための登録は別です。「ビルドは通るのに通知が出ない」ときは、呼び出し側だけでなく、利用する経路・パッケージ形態・昇格の有無を確認します。

従来のToastNotificationManagerを使う場合

package identityを持たないunpackagedアプリでは、AppUserModelID(AUMID)を割り当てたスタートメニューショートカットの登録が前提です。これがないとトーストを出せません。27

MSIXなどでパッケージ化したアプリは、パッケージのidentityがAUMIDを与えるため、この手動登録は不要です。

Windows App SDKのAppNotificationManagerを使う場合

現在推奨されるこの経路では、Windows App SDKの導入と、起動時の Register() 呼び出しが必要です。そのうえで、パッケージ形態によって条件が分かれます。28

パッケージ形態 追加で確認すること
unpackaged 配布先の各PCへのWindows App SDKランタイムの配備が必要。Register() がCOMサーバー登録を行う
MSIXでパッケージ化 Register() による自動登録は働かない。Package.appxmanifest にCOMアクティベーターを宣言する

さらに、Windows App SDK経路では、管理者に昇格したプロセスからの通知は未サポートです。Show は例外を出さず静かに失敗します。昇格が必要なアプリでは、通知だけを非昇格プロセスに任せる分離を検討してください。28

登録まわりの実装の詳細は、トレイ常駐と通知の実装ガイドで扱っています。

トースト通知の2つの経路と必要な登録デスクトップアプリからトースト通知を出すには、従来のToastNotificationManager経路ではpackagedかどうかで分岐し、unpackagedアプリはAppUserModelIDを割り当てたスタートメニューショートカットの登録を経る必要があり、packagedアプリはパッケージのidentityがAppUserModelIDを与える。Windows App SDKのAppNotificationManager経路ではSDKの導入と起動時のRegister呼び出しに加えて、unpackagedアプリは配布先の各PCへのランタイム配備を、MSIXでパッケージ化したアプリはマニフェストへのCOMアクティベーター宣言を満たしてはじめて先へ進める。さらにWindows App SDK経路には昇格プロセスかどうかの分岐があり、管理者に昇格したプロセスからの通知は未サポートでShowは例外を出さず静かに失敗するため、この経路で通知が表示されるのは非昇格プロセスの場合に限られ、昇格が必要なアプリは通知を非昇格プロセスに分離するいいえはいいいえはいいいえはいトーストを出したい従来:ToastNotificationManagerWASDK:AppNotificationManagerpackaged?AUMIDショートカット登録identityがAUMIDを提供SDK導入+Register()packaged?ランタイム配備マニフェストにCOM宣言通知が表示される昇格プロセス?未サポート:Showが静かに失敗通知は非昇格プロセスに分離

図17: どちらの経路も、packaging形態に応じた前提を満たしてはじめて表示に到達できる。Windows App SDK経路は、前提がすべて揃っても昇格プロセスからは表示されない。

最後に、残す・包む・置き換えるへ戻る

新しい機能が必要なのか、UIそのものを刷新したいのか。この問いに戻れば、WinRTを使うことと、既存資産を置き換えることを切り離して判断できます。

既存資産とWinRTの付き合い方の判断フロー既存のデスクトップアプリを起点に、新しいWindows機能が必要なければ残す、機能が必要ならWinRT APIの部分利用で包む(その際は6章のHWND・package identity・スレッド初期化の詰まりどころに注意)、UIそのものの刷新が必要な場合に限ってWinUIへの置き換えを検討する、という段階的な判断の流れ現状で足りる新しい機能UIの刷新既存デスクトップアプリ何が必要?残す(そのまま保守)包む(WinRT API部分利用)置き換え(WinUIを評価)HWND・identity・初期化に注意(6章)

図18: ActiveXの判断表と同じ「残す・包む・置き換える」の構図が、WinRT側にもそのまま当てはまる。

10. まとめ

WinRTは、COMと切り離された新しい実行環境ではありません。COMのバイナリ契約に、メタデータと各言語への投影を組み合わせたAPI基盤です。

要素 この記事で押さえた役割
IUnknownIInspectable QueryInterface・参照カウントを土台とし、型名などを取得する3メソッドを追加する
.winmd 型名から定義を引ける、全言語共通の契約書。ECMA-335形式を借りるが、システム提供のものは実行コードを含まない
C++/WinRT・C#/WinRT 契約書から各言語向けのAPIを生成する。C#の投影は.NET 5以降、ランタイムから独立したツールチェーンになった

見た目がC#やC++の自然なAPIになっても、その下ではvtable経由のCOM呼び出しとHRESULTが使われています。だから、デスクトップでのHWND受け渡し、package identity、STA/MTAとスレッド初期化を理解すると、WinRTのトラブルも切り分けやすくなります。

WinUIの本流開発を公開の場へ移す取り組みは、2025年夏に段階的アプローチとして発表されました。本記事の公開時点では公式ドキュメントにも「built in the open」とありますが、その下の IUnknownIInspectable という契約が変わったわけではありません229

業務アプリでの結論も同じです。既存COM/ActiveX資産とWinRTは共存できます。UIの全面移行とWinRT APIの部分利用を混同しないことが、見積もりと判断の精度を守ります。

OLEの記事で見た1990年代の複合ドキュメントも、現在のWinUIも、同じ IUnknown の上にあります。COMを知っていることは、単に「レガシーに詳しい」ことではありません。今のWindowsの足元を読めることなのです。

関連記事

関連する相談領域

合同会社小村ソフトでは、COMコンポーネント開発と、COM資産を含むWindows業務アプリの保守・改修・リプレイスを扱っています。「WPFのままトースト通知やピッカーを使いたい」「WinRT APIを呼んだら例外で止まった」「WinUIへ移行すべきか、既存資産を活かすべきか判断したい」といった、本記事の内容がそのまま論点になる段階からご相談いただけます。

参考リンク

  1. Microsoft Learn, Author COM components with C++/WinRT. COMコンポーネントとWinRTクラスがどちらもインターフェースで機能を公開すること、「The Windows Runtime is based on COM」の明記、古典COMのインターフェースがIUnknownから、WinRTのインターフェースがIInspectableから派生し、IInspectableがIUnknownから派生すること、IFileSystemBindData2 : IFileSystemBindDataのようなユーザー定義インターフェース同士の派生が古典COMの機能であり、WinRTの型システムには意図的に存在しないことについて。  2 3 4 5 6

  2. Microsoft Learn, Consume COM components with C++/WinRT. COMではオブジェクトではなくインターフェースを介してプログラミングすること、それがCOMの進化(an evolution of COM)であるWinRT APIの舞台裏でも成り立つこと、winrt::com_ptrによるCOMスマートポインタでWinRTと古典COMを同じ流儀で扱えることについて。  2 3 4

  3. Microsoft Learn, Windows Metadata (WinMD) files. WinRT APIが.winmdという機械可読メタデータで記述されツールと言語プロジェクションが利用すること、Windowsがシステム提供の全WinRT APIのメタデータを同梱し解決用APIを提供すること、サードパーティも同形式で言語プロジェクションに参加できること、物理形式がECMA-335仕様(CLRアセンブリと同じ)でありシステム提供のWinMDが純粋なメタデータであることについて。  2 3 4 5

  4. Microsoft Learn, Windows Runtime (WinRT) language projections. 言語プロジェクションがWinRT APIを各言語の慣用に沿って公開すること、.winmdがWinRT APIを定義しプロジェクションがそれを読むこと、MicrosoftがサポートするのはC++/WinRT(C++17以降)とC#/WinRT(.NET)の2つであることについて。  2 3

  5. Microsoft Learn, WinRT APIs callable from a desktop app. ほとんどのWinRT APIが.NETおよびネイティブC++のデスクトップアプリから利用できること、CoreDispatcher・CoreWindow・ApplicationViewなどUWP専用に設計されたクラスが例外であることについて。  2 3 4

  6. Microsoft Learn, Display WinRT UI objects that depend on CoreWindow. 一部のピッカー・ポップアップ・ダイアログがCoreWindowに依存すること、CoreWindowがデスクトップアプリでサポートされないこと、IInitializeWithWindow(または同等のIDataTransferManagerInterop)を実装するクラスには表示前にオーナーウィンドウのHWNDを設定できること、WinUI 3・WPF・WinFormsのそれぞれでの手順について。  2 3 4 5 6

  7. Microsoft Learn, WinRT APIs not supported in desktop apps. デスクトップアプリで使えないWinRT APIの2系統として、UWP専用UI機能に依存するAPIと、package identityを要求するAPI(ToastNotificationHistory・JumpListなど)があること、後者はMSIXでパッケージ化されたアプリでのみサポートされることについて。  2 3 4 5

  8. Microsoft Learn, RoInitialize function (roapi.h). RoInitializeが現在のスレッドを指定した並行性モデル(RO_INIT_SINGLETHREADED/RO_INIT_MULTITHREADED)でWindows Runtime用に初期化すること、WinRTオブジェクトを活性化・操作するすべてのスレッドが事前の初期化を必要とすること、MTAとして初期化済みのスレッドで矛盾する指定をするとRPC_E_CHANGED_MODEになることについて。  2

  9. Microsoft Learn, WinUI 3. WinUIが新規Windowsデスクトップアプリ向けに推奨されるネイティブUIフレームワークであること、Windows App SDKの一部として提供されること、Windows 10 バージョン1809以降で動作すること、公開の場で開発されていることについて。  2 3 4 5

  10. Microsoft Learn, Call Windows Runtime APIs in desktop apps. .NET 6以降でWindows OSバージョン付きTFM(net10.0-windows10.0.22621.0など)を指定するとWindows SDK targeting packageが参照されWinRT APIを呼べること、C++はMicrosoft.Windows.CppWinRT NuGetパッケージとC++17以降でC++/WinRTを使うことについて。  2 3 4

  11. Microsoft Learn, Introduction to C++/WinRT. C++/WinRTが完全に標準の現代C++17による言語プロジェクションでありヘッダーファイルベースのライブラリとして実装されること、C++/CXとWRLの推奨後継であること、WinRTがCOM APIに基づき言語プロジェクションを通じてアクセスされるよう設計されていること、プロジェクションがCOMの詳細を隠すこと、cppwinrt.exeが.winmdから投影ヘッダーを生成することについて。  2 3

  12. Microsoft Learn, The Windows Runtime (WinRT) type system. すべてのWinRTインターフェースが暗黙にIInspectableを要求しIInspectableがIUnknownを要求すること、IUnknownがQueryInterface・AddRef・Releaseを定義すること、IInspectableが追加するGetIids・GetRuntimeClassName・GetTrustLevelの3メソッド、GetRuntimeClassNameがメタデータで解決可能な型名を返すことで言語プロジェクションを可能にすること、ユーザー定義インターフェース同士の継承がWinRTの型システムに無く、requiresで表現することについて。  2 3 4

  13. Microsoft Learn, IInspectable interface (inspectable.h). IInspectableがすべてのWinRTクラスに必要な機能を提供すること、IUnknownを継承すること、GetIids・GetRuntimeClassName・GetTrustLevelの3メソッドを持つことについて。 

  14. Microsoft Learn, Introduction to Microsoft Interface Definition Language 3.0. MIDL 3.0がWinRT型を宣言するための簡潔な現代的構文であること、WinRTの契約は今もIDLで記述され、MIDLコンパイラがWindowsメタデータ(.winmd)を生成することについて。 

  15. Microsoft Learn, C#/WinRT. C#/WinRTのNuGetパッケージに含まれるcswinrt.exeが.winmdを処理してC#コードを生成し相互運用アセンブリにコンパイルすること、C++/WinRTがC++向けヘッダーを生成するのと同様の位置づけであることについて。 

  16. Microsoft Learn, Built-in support for WinRT is removed from .NET. .NET 5でWindows Runtimeの組み込みサポートが.NETから削除され、CsWinRTツールチェーンに移行したことについて。  2

  17. Microsoft Learn, IInitializeWithWindow interface (shobjidl_core.h). デスクトップアプリで使われるWinRTオブジェクトにオーナーウィンドウを提供するためのインターフェースであること、IUnknownを継承しInitialize(HWND)メソッドを持つことについて。 

  18. Microsoft Learn, Use WinRT COM interop classes in .NET. ファイルピッカーやダイアログなど一部のWinRTオブジェクトがデスクトップアプリで動作する前にHWNDを必要とすること、WinRT.Interop.WindowNativeとWinRT.Interop.InitializeWithWindowという型安全なC#クラスで手書きのQueryInterface呼び出しなしに初期化できることについて。  2

  19. Microsoft Learn, Tutorial: Open files and folders with pickers in WinUI. 従来のWindows.Storage.Pickersをデスクトップ(WinUI 3)アプリで使う場合は表示前にHWNDで初期化しなければ例外または沈黙の失敗になること、WinUI 3デスクトップアプリにCoreWindowがないこと、新しいWindows App SDKのピッカー(Microsoft.Windows.Storage.Pickers)はコンストラクタでWindowIdを受け取りInitializeWithWindowパターンが不要であることについて。  2 3

  20. Microsoft Learn, Features that require package identity. 一部のWindows機能とWinRT APIが実行時にpackage identityを要求すること、MSIXパッケージでの配布に加え、外部の場所を指すパッケージ(packaged with external location)でも識別子を得られることについて。  2

  21. Microsoft Learn, CoInitialize function (objbase.h). CoInitializeがCOMライブラリをSTAとして初期化すること、新しいアプリはCoInitializeExを呼ぶべきこと、Windows Runtimeを使う場合は代わりにRoInitializeまたはWindows::Foundation::Initializeを呼ばなければならないことについて。 

  22. GitHub, WinUI: Now Developing in the Open (microsoft/microsoft-ui-xaml Discussion #10700). 2025年7月末の公式アナウンス。WinUIのリポジトリを公開していくための段階的アプローチ(ミラー更新頻度の向上、ローカルビルドの実現、テストを整えたうえでのコミュニティ貢献の受け付け、最終的にGitHubを開発の主本拠にすること)が示されていることについて。  2 3

  23. Microsoft Learn, Windows App SDK. Windows App SDKがWinUIを含む現行のWindowsアプリ開発ライブラリ群であることについて。 

  24. Microsoft Learn, Walkthrough: WinUI 3 app with Win32 interop. WinUIのWindowクラスがデスクトップウィンドウをサポートするよう拡張されたこと、WinUI 3のデスクトップアプリではWindowがWin32のウィンドウハンドル(HWND)に裏打ちされており、ウィンドウハンドルを取得してWin32 APIで操作できることについて。 

  25. Microsoft Learn, Host UWP XAML controls in desktop apps (UWP XAML Islands). UWP世代のXAML IslandsがWPF・WinForms・C++デスクトップアプリにUWP XAMLコントロールを載せる仕組みであること、WPF/WinFormsでの利用が.NET Core 3.x対象のアプリに限られ、現行の.NETや.NET Frameworkではサポートされないことについて。  2

  26. Microsoft Learn, DesktopWindowXamlSource Class (Microsoft.UI.Xaml.Hosting). Windows App SDKのXAMLホスティングAPIの中核クラスであり、HWNDに関連付けられた任意のUI要素にWinUIコントロールをホストできること、WPF・Windows Forms・Win32(Windows API)で作られたデスクトップアプリから利用できることについて。  2

  27. Microsoft Learn, Quickstart: Sending a toast notification from the desktop. デスクトップアプリからのトースト送信には、System.AppUserModel.IDを設定したスタートメニューのショートカットが前提であること、CreateToastNotifierの呼び出しにそのAppUserModelIDを渡す必要があり、無ければトーストが表示されないことについて。 

  28. Microsoft Learn, Use app notifications with a .NET app. WPF/WinFormsアプリでWindows App SDKのAppNotificationManagerを使うにはNotificationInvokedハンドラーの登録後にRegister()を呼ぶ必要があること、unpackagedアプリではRegister()が通知クリック時にアプリを起動するためのCOMサーバー登録を自動で行うこと、前提としてWindows App SDKの導入とWinRT API呼び出しの構成が必要なことについて。  2

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

WinRTは.NETのようなマネージドランタイムですか?
違います。WinRT(Windows Runtime)は仮想マシンやガベージコレクタを持つ実行環境ではなく、COMを土台にしたABI(バイナリ契約)です。Microsoft自身のドキュメントが「The Windows Runtime is based on COM」と明記しており、すべてのWinRTインターフェースはIUnknown由来のIInspectableを要求します。呼び出しの実体は今もvtable経由のCOM呼び出しで、参照カウント(AddRef/Release)とQueryInterfaceの上に成り立っています。「.winmd」という名前や.NETとの親和性の高さからマネージド環境と誤解されがちですが、.winmdはECMA-335と同じ物理形式を借りたメタデータファイルであって、システム提供の.winmdは実行コードを含みません。C#から自然に呼べるのは、言語プロジェクションがメタデータからC#向けの投影を生成しているからです。
UWPはもう主流ではないと聞きました。いまさらWinRTを学ぶ意味はありますか?
あります。UWPというアプリモデルとWinRTというAPI基盤は別物です。UWPの縮小後も、WinRT APIの大半はWPF・WinForms・Win32のデスクトップアプリから呼び出せる形で提供され続けており、トースト通知・共有・Bluetooth・OCRなど「今のWindowsの機能」の多くはWinRT APIとして公開されています。さらに、Microsoftが現在推奨するネイティブUIフレームワークであるWinUI(Windows App SDK)は、WinRTのABIの上に建っています。つまりWinRTの仕組み(IInspectable・.winmd・言語プロジェクション)は、UWPの遺産ではなく、現行のWindowsアプリ開発の足元そのものです。
WPFやWinFormsのアプリからWinRT APIを呼べますか?
呼べます。.NET 6以降ならプロジェクトファイルのTargetFrameworkをnet8.0-windows10.0.19041.0のようなWindows OSバージョン付きのTFMにするだけで、Windows SDKの投影アセンブリが参照され、Windows.*名前空間のWinRT APIをC#から直接呼べます。C++ならMicrosoft.Windows.CppWinRT NuGetパッケージを導入し、C++17以降でC++/WinRTを使います。ただし3つの詰まりどころがあります。第一に、ピッカーやダイアログなどCoreWindow前提のUI系クラスは、表示前にIInitializeWithWindowでオーナーウィンドウのHWNDを渡す必要があります(ただし共有UIのDataTransferManagerは例外で、IInitializeWithWindowではなく専用のIDataTransferManagerInteropを使い、ShowShareUIForWindowにHWNDを渡す別経路です)。第二に、通知履歴やジャンプリストなど一部のAPIはpackage identity(MSIXパッケージ化または外部の場所を指すパッケージ)を要求します。第三に、WinRTオブジェクトを扱うスレッドには事前の初期化が必要です。ネイティブコードではwinrt::init_apartmentやRoInitializeでSTA/MTAの並行性モデルを指定します(C#のWPF/WinFormsアプリでは通常ランタイム側が面倒を見ます)。なお、CoreWindowやApplicationViewそのものに依存するAPIは、そもそもデスクトップアプリでは使えません。
デスクトップアプリでFolderPickerなどのピッカーを呼ぶと例外になります。なぜですか?
ピッカーやダイアログの一部のWinRTクラスが、表示先としてUWPのCoreWindowを前提に設計されているためです。デスクトップアプリにはCoreWindowが存在しないので、表示前にオーナーウィンドウを明示的に教える必要があります。具体的には、まずオーナーウィンドウのHWNDを取得し(WinUIのWindowならWinRT.Interop.WindowNative.GetWindowHandle、WPFならWindowInteropHelper、WinFormsならフォームのHandleプロパティ)、C#ならWinRT.Interop.InitializeWithWindow.Initializeでピッカーに渡します。C++/WinRTならオブジェクトをIInitializeWithWindowにQueryInterfaceして(as<IInitializeWithWindow>())、Initialize(hwnd)を呼びます。この初期化をしないと、例外になるか黙って失敗します。なお、新しいWindows App SDKのピッカー(Microsoft.Windows.Storage.Pickers)はコンストラクタでWindowIdを受け取る設計に改められており、この初期化パターン自体が不要になっています(ただしWindows App SDKのAPIなので、TFMの設定だけでは使えず、SDKの導入と、unpackagedアプリなら配布先へのランタイムの配備・初期化が前提です)。
WinUIの開発がGitHubで公開されたそうですが、既存のWPF/WinFormsアプリは捨てるべきですか?
急いで捨てる必要はありません。WinUIの本流開発の公開は「Microsoftがネイティブフレームワークへ本気で投資する」という方向の表明であり、既存フレームワークの打ち切り宣言ではありません。WPFとWinFormsは現在も.NETの一部としてサポートされ続けています。判断を2つに分けてください。ひとつはUIフレームワークを移行するかという判断で、これは画面資産の規模・サードパーティコントロール・開発体制に依存する大きな話です。もうひとつはWinRT APIを既存アプリから部分利用するかという判断で、こちらはTFMの設定で今日から始められます。ただしTFMはAPIを呼べるようにするだけで、たとえばトースト通知には呼び出しに加えて通知の登録が必要です(従来経路はunpackagedアプリの場合にAppUserModelID付きショートカットの登録 ── パッケージ化済みならパッケージのidentityがAppUserModelIDを与えるため不要 ── 、Windows App SDK経路はSDKの導入 ── unpackagedアプリなら配布先の各PCへのWindows App SDKランタイムの導入も ── とAppNotificationManagerのRegister()呼び出しで、MSIXでパッケージ化したアプリではRegister()による自動登録は働かず、さらにPackage.appxmanifestへのCOMアクティベーターの宣言が必要です)。また、Windows App SDK経路では管理者に昇格したプロセスからの通知は未サポートで、Showは例外を出さず静かに失敗します ── 昇格が必要なアプリは、通知だけ非昇格プロセスに任せる分離を検討してください。それでも、トースト通知が欲しいだけならWinUIへの移行は不要です。なお、既存WPF/WinForms画面へのWinUIコントロールの部分混在(XAML Islands)は世代の区別が必要です。UWP世代のIslandsはWPF/WinForms対応が.NET Core 3.x止まりで、WinUI 3世代にはWindows App SDKのXAMLホスティングAPI(DesktopWindowXamlSource)がWPF/WinFormsからも使えますが、便利なラッパーコントロールが無く実装負担が大きいため、現時点では慎重に評価するのが安全です。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る