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

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

「トースト通知を出したいだけなのに、ピッカーを呼んだら例外で落ちた」「WinUIの開発がGitHubで公開されたらしいが、また乗り換えを迫られるのか」「WinRTはUWPと一緒に終わった新技術ではないのか」── 2026年のWindowsデスクトップ開発の困りごとと不安をたどっていくと、実は35年前から変わらない同じ場所に行き着きます。COMのバイナリ契約です。

前回のOLEオブジェクトの記事で、WordにExcelの表を埋め込むあの機能の足元がCOMであることを見ました。実は「今のWindowsの表側」であるWinRT(Windows Runtime)も、根は同じです。Microsoft自身がドキュメントで「The Windows Runtime is based on COM(Windows RuntimeはCOMに基づく)」と明記しています。1 OLEもWinRTも、IUnknown から始まる同じバイナリ契約の上に建っている ── この一点を押さえると、WinRTの見え方が変わります。

この記事では、WPF・WinForms・Win32で業務アプリを作ってきた開発者を対象に、WinRTの正体(COM+メタデータ+言語プロジェクション)、IUnknown の上に乗る IInspectable、型ライブラリと言語ごとのバインディングの苦労を解決した .winmd、C++/WinRT・C#/WinRTが「ラッパー」ではなく投影である理由、そしてデスクトップアプリからWinRT APIを呼ぶときに詰まるポイント(HWND初期化・package identity・アパートメント)までを一枚につなぎます。対象読者はCOMまたはWindowsデスクトップ開発の経験がある開発者、前提環境はWindows 10/11と.NET 6以降(C#)またはC++17(C++/WinRT)、難易度は中級です。

1. まず結論

  • WinRTはマネージドランタイムではありません。COMを土台にしたABI(バイナリ契約)に、メタデータ(.winmd)と言語プロジェクションを足したものです。Microsoftのドキュメントは「WinRTはCOMに基づく」「COMの進化」と繰り返し書いています。12
  • すべてのWinRTインターフェースは IInspectable を要求し、IInspectableIUnknown を要求します。QueryInterfaceAddRefRelease は今もすべての呼び出しの土台です。3
  • IInspectable が足すのは GetIidsGetRuntimeClassNameGetTrustLevel の3メソッドだけです。特に GetRuntimeClassName が返す型名をメタデータで解決できることが、言語プロジェクションを可能にしています。34
  • .winmd は、古典COMで言語ごとにばらけていた型情報の配布(型ライブラリ・ヘッダー・相互運用アセンブリ)を、全言語共通の機械可読メタデータに置き換えたものです。契約を記述するIDL自体はMIDL 3.0としてWinRTでも現役で、そのコンパイル成果物が .winmd になります。物理形式はECMA-335(CLRアセンブリと同じ)を借りていますが、システム提供の .winmd は実行コードを含まない純粋なメタデータです。56
  • C++/WinRT・C#/WinRTは「ラッパーライブラリ」ではなく投影(projection)です。メタデータを読んで、各言語の慣用に沿ったAPIをツールが生成します。.NET 5でWinRTサポートは.NET本体から取り除かれ、C#/WinRT(CsWinRT)という独立したツールチェーンに移りました。78
  • WinRT APIの大半はWPF・WinForms・Win32のデスクトップアプリから呼べます。詰まりどころは主に3つ ── CoreWindow前提のUI系クラス(IInitializeWithWindow、共有UIは専用の IDataTransferManagerInterop でHWNDを渡す)、package identity必須のAPI(パッケージ化が必要)、スレッドの初期化(ネイティブコードでの RoInitialize / winrt::init_apartment とSTA/MTAの選択 ── C#では通常ランタイム側が初期化します)です。91011
  • WinUI(Windows App SDK)はこのWinRT ABIの上のUIフレームワークです。開発をGitHubの公開の場へ移す方針は2025年夏に段階的アプローチとして発表され、本記事の公開時点では公式ドキュメントも「WinUIは公開の場で開発されている(built in the open)」と明記しています。開発体制が変わっても、足元の契約は IUnknownIInspectable のまま変わっていません。1213
  • 既存のCOM/ActiveX資産とWinRTは排他ではありません。同じCOM基盤の上で共存でき、「UIをWinUIへ全面移行するか」と「WinRT APIを必要な箇所だけ呼ぶか」は別の判断です(9章の判断表)。

この記事の知識マップ

WinRT(Windows Runtime)はマネージドランタイムではなく、COMを土台にしたABIにメタデータ(.winmd)と言語プロジェクションを足したものです。すべてのWinRTインターフェースはIInspectableを要求し、IInspectableはIUnknownを要求します。QueryInterface・AddRef・Releaseは今もすべての呼び出しの土台であり、IInspectableが追加するのはGetIids・GetRuntimeClassName・GetTrustLevelの3メソッドだけです。GetRuntimeClassNameが返す型名をメタデータで解決できることが言語プロジェクションを可能にしています。.winmdはECMA-335(CLRアセンブリと同じ)の物理形式を借りた機械可読メタデータで、システム提供のものは実行コードを含みません。C++/WinRTとC#/WinRT(CsWinRT)はこのメタデータから各言語の慣用に沿ったAPIを生成する投影であり、.NET 5で組み込みWinRTサポートが削除されて以降、C#の投影は独立したツールチェーンになりました。WinRT APIの大半はWPF・WinForms・Win32のデスクトップアプリから呼べますが、ピッカーなどCoreWindow依存のUI系クラスは表示前にIInitializeWithWindow(共有UIのDataTransferManagerは専用のIDataTransferManagerInterop)でオーナーウィンドウのHWNDを渡す必要があり、通知履歴などpackage identity必須のAPIはMSIXまたは外部の場所を指すパッケージによるパッケージ化を要求し、スレッドは事前のWinRT用初期化が必要です(ネイティブコードではRoInitializeやwinrt::init_apartmentでSTA/MTAの並行性モデルを指定し、C#のWPF/WinFormsアプリでは通常ランタイム側が処理します)。WinUIはWindows App SDKの一部として提供されるWinRT ABIの上のUIフレームワークで、既存WPF/WinForms画面へのWinUIコントロールの段階混在(XAML Islands)は世代ごとに対応と制約が異なります。

WinRTとCOMの知識マップWinRTがCOMを前提としIInspectableと.winmdを利用する構造、すべてのWinRTインターフェースが要求するIInspectableがIUnknownを前提とすること、.winmdがECMA-335形式を利用し言語プロジェクションがそれを読むこと、C++/WinRTとC#/WinRTが言語プロジェクションの実装を担い、C#/WinRTが.NET組み込みWinRTサポートの後継であること、WinUIがWindows App SDKとWinRTを前提とすること、CoreWindow依存のWinRT UIがデスクトップアプリではIInitializeWithWindowなどUI固有のinteropによるHWNDの受け渡しを前提とすること、package identity必須のWinRT APIがpackage identityを前提とし、それがMSIXまたは外部の場所を指すパッケージで構成できること、RoInitializeがSTA/MTAというCOMのアパートメントモデルを利用すること、既存WPF/WinFormsへのWinUI段階移行が制約付きのXAML Islandsを利用することを示す図前提とする前提とする前提とする利用する利用する利用する実装を担う実装を担うの後継前提とする前提とする前提とする利用する前提とする利用する前提とするで構成できるで構成できる利用する利用するWinRT(Windows Runtime)COM(コンポーネントオブジェクトモデル)IInspectableIUnknownWindows Metadata(.winmd)ECMA-335言語プロジェクションC++/WinRTC#/WinRT(CsWinRT).NET組み込みWinRTサポートWinUI(Windows App SDK)Windows App SDKCoreWindow依存のWinRT UIIInitializeWithWindowHWND(ウィンドウハンドル)IDataTransferManagerInteroppackage identity必須のWinRT APIpackage identityMSIXパッケージング(packaged)外部の場所を指すpackaged(sparse package)RoInitializeCOMアパートメントモデル(STA/MTA)既存WPF/WinFormsへのWinUI段階移行XAML Islands

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全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の足元にもなっています。14

新旧まったく別の世界に見えますが、実体は地続きです。COMコンポーネントとWinRTクラスは、どちらもインターフェースを通じて機能を公開します。違いはただひとつの約束ごとに集約されます ── 古典COMのインターフェースが最終的に IUnknown から派生するのに対し、WinRTのインターフェースは最終的に IInspectable から派生し、その IInspectableIUnknown から派生する、という一段の積み増しです。1 参照カウントもQueryInterfaceもHRESULTも、そのまま生きています。C++/WinRTの入門ドキュメントはさらに率直で、WinRT APIを「COMの進化(an evolution of COM)」と呼び、「Windows RuntimeはCOMに基づいており、言語プロジェクションを通じてアクセスされるように設計されている」と説明しています。215

古典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

WinRTの型システムを規定する公式仕様は、インターフェースの土台をこう定めています ── すべてのWinRTインターフェースは暗黙に IInspectable を要求し、IInspectableIUnknown を要求する。IUnknown は従来のCOMの用法どおり QueryInterfaceAddRefRelease の3メソッドを定義する。3

IInspectable が追加するのは、次の3メソッドだけです。4

メソッド 役割
GetIids このオブジェクトが実装するインターフェースのIID一覧を返す
GetRuntimeClassName 完全修飾されたWinRT型名(Windows.Storage.StorageFile など)をHSTRINGで返す
GetTrustLevel オブジェクトの信頼レベルを返す

たった3つですが、この追加が古典COMとの決定的な違いを生みます。古典COMでは、実行時にオブジェクトの素性を知る標準の方法は「IIDを知っていて QueryInterface で聞く」ことでした(スクリプト言語向けには IDispatch という別経路がありました)。WinRTでは、任意のオブジェクトに GetRuntimeClassName を呼べば型名の文字列が返り、その型名をメタデータ(.winmd ── 4章)で解決すれば、メソッド・プロパティ・イベントの完全な定義が手に入ります。仕様自身が「GetRuntimeClassName は、メタデータで解決可能なWinRT型名を取得させることで言語プロジェクションを可能にする(enables language projection)」と述べています。3

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が乗る。

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

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

もうひとつ、COMに慣れた人ほど気づく違いがあります。WinRTの型システムには、ユーザー定義インターフェース同士の継承がありません。古典COMでは IFileSystemBindData2 : IFileSystemBindData のような派生が普通でしたが、WinRTはこれを意図的に持たず、代わりに「インターフェースAはインターフェースBを要求する(requires)」という宣言で表現します(ここまで見た IUnknownIInspectable という基底のABIチェーンは別で、これはすべてのWinRTインターフェースの土台として残っています)。31 vtableの継承レイアウトに依存しない、より疎な契約に寄せた設計です。とはいえこれは契約の書き方の違いであって、呼び出しの実体がvtable経由であることは変わりません。

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

古典COMの実務でいちばん手間がかかったのは、インターフェースの実装そのものより「型情報をどう配るか」でした。契約はIDLで書き、MIDLコンパイラでC++用のヘッダーとプロキシ/スタブを生成する。VB6やスクリプトには型ライブラリ(TLB)を配る。TLBはオートメーション寄りの型に制約があり、IDLに書けてもTLBに入らない情報がある。.NETから使うには相互運用アセンブリを別途作る ── 言語ごとに型情報の経路がばらばらで、どれか一つが古くなると型の不一致という嫌なバグになりました。この苦労は型ライブラリとdscomの記事DLL・COMインターフェースの後方互換性の記事で扱ったとおりです。

WinRTの答えが Windows Metadata(.winmd) です。公式ドキュメントの要点はこうです。5

  • WinRT APIは .winmd という機械可読メタデータファイルで記述され、ツールと言語プロジェクションがこれを読んで投影を生成する
  • Windowsはシステム提供の全WinRT APIのメタデータを同梱し、実行時に名前空間や型を解決するAPIも提供する。Windows SDKにはコンパイル時用のコピーが入っている。
  • サードパーティも自分のWinRTコンポーネントに .winmd を付ければ、システムAPIと同じ仕組みで言語プロジェクションに参加できる。
  • 物理形式はECMA-335仕様(CLRアセンブリと同じ)を使う。ただし有効なデータの組み合わせの規則はCLRアセンブリと異なり、システム提供の .winmd は実行コードを含まない純粋なメタデータである。

誤解のないように補足すると、「IDLが不要になった」わけではありません。WinRTコンポーネントを自分で作るときは、今もIDL(WinRT向けに刷新されたMIDL 3.0)で契約を記述し、MIDLコンパイラがそれを .winmd に落とします。6 変わったのは配布される型情報の形です ── 言語ごとにばらけていたTLB・ヘッダー・相互運用アセンブリが、単一の .winmd を全言語のプロジェクションが読む形に共通化されました。

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

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

最後の点が「WinRT=マネージド」という誤解の出どころです。.winmd をツールで開くと.NETアセンブリのように見えます。しかしそれは形式を借りただけで、中身は「COMインターフェースの契約書」です。システム提供のWinRT APIでは、実装はOSのネイティブDLLにあり、実行にCLRは要りません。なお、これはシステムAPIに限った話です ── サードパーティのWinRTコンポーネントの .winmd は実装コードを含むこともあり、特にマネージドな(C#で書かれた)コンポーネントの .winmd はMSILを含むため、その実行には対応する.NETランタイムが必要です。5

.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章で見たユーザー定義インターフェース継承の不在はその一例です ── に置き換わった、というのが正確な見立てです。3 したがって、既存の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の詳細を隠し、その言語にとって自然なプログラミング体験を提供する」というものです。715

Microsoftが現在サポートする投影は2つです。7

  • C++/WinRT ── 標準C++17で実装されたヘッダーファイルベースの投影。cppwinrt.exe.winmd を与えると、そのAPIをC++から自然に呼べるヘッダー群が生成されます。C++/CXのような言語拡張を使わず、標準準拠のC++17コンパイラだけで書けるのが特徴で、C++/CXとWRLの後継に位置づけられています。15
  • C#/WinRT(CsWinRT) ── .NET向けの投影。cswinrt.exe.winmd を処理してC#の相互運用アセンブリを生成します。16 かつて.NET(〜.NET Core 3.x)はWinRT/winmdの消費をランタイム組み込みでサポートしていましたが、.NET 5でこの組み込みサポートは削除され、投影はランタイムから独立したツールチェーンになりました。8 いまC#で net8.0-windows10.0.19041.0 のようなTFMを指定すると、Windows SDKの投影アセンブリが自動参照されます。14
.winmdから各言語への投影の生成単一の.winmdをcppwinrt.exeが読むとC++17向けの投影ヘッダーが、cswinrt.exeが読むとC#向けの相互運用アセンブリが生成され、それぞれの言語の慣用に沿った形でWinRT APIが公開される.winmd(APIの契約書)cppwinrt.exe→C++17ヘッダーcswinrt.exe→C#相互運用アセンブリC++の慣用で呼べるC#の慣用で呼べる

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

「ラッパー」と「投影」の違いは、単なる言葉の好みではありません。ラッパーは人が書いた翻訳層で、APIの追加のたびに人が追従します。投影はメタデータからの機械的な導出なので、.winmd に載っているAPIはすべて、最初から全対応言語で使えます。そしてどの言語から呼んでも、投影の下で起きていることは同じCOM呼び出しです。C#で await picker.PickSingleFolderAsync() と書いたとき、投影が IAsyncOperation というWinRTインターフェースを.NETの Task の世界に橋渡ししていますが、ワイヤの上を流れるのは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そのものではない。

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

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

WinRT APIの大半は、WPF・WinForms・Win32のデスクトップアプリから呼べます。17 設定も難しくありません(.NETはTFM、C++はCppWinRT NuGet14)。それでも現場で毎回同じ場所で詰まります。詰まりどころは構造的に3つです。

詰まりどころ1: CoreWindow前提のUI系クラス。ピッカー・ダイアログ・共有UIなど一部のWinRTクラスは、表示先としてUWPの CoreWindow を前提に設計されています。デスクトップアプリに CoreWindow はないので、表示前にオーナーウィンドウのHWNDを明示的に渡す必要があります。9 その口が IInitializeWithWindow というCOMインターフェース ── 「デスクトップアプリで使われるWinRTオブジェクトにオーナーウィンドウを提供する」ための、IUnknown 直系の古風なインターフェースです。18 なお共有UIの DataTransferManager はこのインターフェースではなく、同じ役割の専用インターフェース IDataTransferManagerInterop を使い、ShowShareUIForWindow にHWNDを渡す別経路になっています。9 C#なら、まずオーナーウィンドウのHWNDを取得し ── WinUIの Window なら WinRT.Interop.WindowNative.GetWindowHandle、WPFなら WindowInteropHelper、WinFormsならフォームの Handle プロパティ ── そのうえで WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd) を呼びます。19 C++/WinRTならオブジェクトを as<IInitializeWithWindow>() してから Initialize(hwnd) です。この初期化をしないと、例外になるか、黙って失敗します20 WinRTが「新しい別世界」ではなくCOMである証拠のような仕様で、モダンなWinRTオブジェクトに古典COMのインターフェースをQueryInterfaceして初期化する、という橋渡しが公式の作法になっています。なお、新しいWindows App SDKのピッカー(Microsoft.Windows.Storage.Pickers)はコンストラクタで WindowId を受け取る設計に改められ、このパターン自体が不要になりました(ただしこちらはWindows App SDKのAPIなので、TFMの設定だけでは使えず、SDKの導入と、unpackagedアプリならランタイムの配備・初期化が前提です)。20

デスクトップアプリからピッカーを表示する手順デスクトップアプリがピッカーを生成した後、そのまま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を持つアプリ(パッケージ化されたアプリ)でしか動きません10 従来型のインストーラーで配るunpackagedなアプリから呼ぶと失敗します。対処は、MSIXでパッケージ化するか、既存インストーラーを保ったまま識別子だけを付与する「外部の場所を指すパッケージ(いわゆるsparse package)」を使うかです。21 どのAPIがidentityを要求するかは公式の一覧で確認できます。10

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

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

詰まりどころ3: スレッドの初期化とアパートメント。WinRTのオブジェクトを扱うスレッドは、事前にWinRT用の初期化が必要です。ネイティブコードでは RoInitialize を呼び、STA/MTAという並行性モデルを指定します ── COMの CoInitializeEx と同じ枠組みの、WinRT世代の入り口です。11 実際、CoInitialize のドキュメント自身が「Windows Runtimeを使うなら、代わりに RoInitialize または Windows::Foundation::Initialize を呼ぶこと」と案内しています。22 WPF/WinFormsのUIスレッドがSTAであること、UIオブジェクトはUIスレッドで触ること、ブロッキング待機がSTAでデッドロックを招くこと ── STA/MTAの記事で扱った考え方は、WinRT APIの利用でもそのまま生きています。

デスクトップから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段階です。12 本記事の公開時点(2026年8月末)では、公式ドキュメントも「WinUIは公開の場で開発されている(built in the open)」と明記し、公開リポジトリで日々のエンジニアリングの進捗を追えるようになっています。13

「またフレームワークが変わるのか」という反応が出るのは自然です。WinForms→WPF→UWP→WinUIと、UIフレームワークの世代交代を追いかけてきた開発者ほどそう感じるはずです。しかし本記事の視点から見ると、風景は逆です。UIフレームワークが浮き沈みしてきた15年間、その下の土台 ── Win32とCOM、そして2012年以降のWinRT ABI ── は同じ場所にあり続けました。WinUIはWindows App SDKの一部として提供されるWinRTのAPI群であり1323、その 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である。積み方が変わっただけで土台は同じ。

ひとつ現実的な注意があります。「既存のWPF/WinForms画面に、WinUIコントロールを部分的に混ぜて見た目だけ新しくする」というXAML Islands型の戦略は、世代の区別が必要です。UWP世代のXAML Islands(Windows Community Toolkitのラッパーコントロール)は、WPF/WinForms向けの対応が.NET Core 3.x世代で止まっており、現行の.NETではサポートされません。25 一方、WinUI 3世代にはWindows App SDKのXAMLホスティングAPI(DesktopWindowXamlSource)があり、WPF・WinForms・Win32のアプリからWinUIコントロールをホストできます。26 ただしUWP世代にあったような便利なラッパーコントロールは提供されておらず、ホスティングAPIを直接使う実装・検証の負担は小さくありません。段階混在を計画するなら、コントロール単位の混在より、機能単位のWinRT API利用(6章)や画面・プロセス単位の分離を軸にするほうが、2026年時点では堅実です。

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

受託の現場でくり返し出会う誤解が「WinRTは新しい別世界で、使うには既存資産を捨てて作り直すしかない」というものです。ここまで見たとおり、これは二重に違います。

第一に、WinRTと既存COM資産は同じ土台の上にあり、共存できます。Excel COMオートメーションを使い、ActiveXコントロールを載せ、自社COMコンポーネントを呼んでいるWPFアプリに、TFMを設定してトースト通知のWinRT APIを足す ── これは特別な曲芸ではなく、同じCOM基盤の上の普通の組み合わせです。C++/WinRTに至っては、WinRTと古典COMの両方のインターフェースを同じ仕組み(winrt::com_ptrwinrt::implements)で扱えます。21

第二に、「UIをWinUIへ全面移行するか」と「WinRT APIを必要な箇所だけ使うか」は、規模も期間もリスクも桁が違う別の判断です。前者はフレームワーク選定の問題で、画面資産・サードパーティコントロール・開発体制に依存します(WinForms/WPF/WinUIの選び方で扱いました)。後者は今日からできる小さな改善です。トースト通知が欲しいだけの案件で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章)、トーストは表の下の補足を参照149
ピッカー・ダイアログで例外/沈黙の失敗 IInitializeWithWindow でHWNDを渡す。新規はWindowId対応の新ピッカー(Windows App SDKの導入が前提) CoreWindow前提の設計をHWNDで埋める公式の作法920
通知履歴・ジャンプリストなどが動かない MSIXまたは外部の場所を指すパッケージでpackage identityを付与 identity必須APIはパッケージ化が前提1021
既存のCOM/ActiveX/OLE資産 捨てない。残す/包む/置き換えは資産ごとに判断 WinRTと排他ではなく同じ土台で共存できる(8章)
新規デスクトップアプリのUI WinUIを第一候補として評価(WPFも現役) 公開開発で投資方向が明確化。足元はWinRT ABI1213
既存WPF/WinForms画面へWinUIコントロールを部分混在 ラッパー不在の実装負担を見込んで慎重に評価 UWP世代Islandsは.NET Core 3.x止まり、WinUI 3世代はホスティングAPIのみ2526
「WinRTはUWPと一緒に終わった」という前提の計画 前提を修正する WinRTはデスクトップから呼べる現行のAPI基盤17

トースト通知だけは補足が必要です。TFMを設定してAPIが呼べるようになっても、それだけでは通知は画面に出ません。従来の ToastNotificationManager 経路では、package identityを持たないunpackagedアプリの場合、AppUserModelID(AUMID)を割り当てたスタートメニューショートカットの登録が前提で、これが無いとトーストを出せません(MSIXなどでパッケージ化したアプリはパッケージのidentityがAUMIDを与えるため、この手動登録は不要です)。27 現在推奨されるWindows App SDKの AppNotificationManager 経路では、Windows App SDKの導入(unpackagedならランタイムも)と、起動時の Register() 呼び出しが必要です(unpackagedアプリではこの Register() がCOMサーバー登録まで面倒を見てくれます。逆にMSIXでパッケージ化したアプリでは自動登録は働かず、Package.appxmanifestへのCOMアクティベーターの宣言が必要です)。28 さらに、この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経路は、前提がすべて揃っても昇格プロセスからは表示されない。

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

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

10. まとめ

  • WinRTはマネージドランタイムではない。COMにメタデータ(.winmd)と言語プロジェクションを足したABIであり、Microsoft自身が「COMに基づく」「COMの進化」と明記している。
  • すべてのWinRTインターフェースは IInspectable を要求し、IInspectableIUnknown を要求する。QueryInterface・参照カウント・HRESULTは今も現役の土台。
  • IInspectable が足すのは3メソッドだけ。GetRuntimeClassName の型名を .winmd で解決できることが、全言語共通の投影を可能にした。.winmd は「型ライブラリのやり直し」であり、ECMA-335形式を借りたメタデータ(システム提供のものは実行コードを含まない)。
  • C++/WinRT・C#/WinRTはラッパーではなく投影。.NET 5以降、投影はランタイムから独立したツールチェーン(CsWinRT)になった。呼び出しの実体はどの言語でもvtable経由のCOM。
  • デスクトップからの利用で詰まるのは、CoreWindow前提のUI系(HWNDを渡す)、package identity必須API(パッケージ化)、スレッド初期化(ネイティブコードでは RoInitialize でSTA/MTAを指定。C#は通常ランタイム任せ)の3系統。どれも定型の対処がある。
  • WinUIはWinRT ABIの上のUIフレームワークで、その本流開発はGitHubの公開の場へ移りつつある(2025年夏に段階的アプローチを発表、現在は公式ドキュメントも「built in the open」と明記)。フレームワークの世代交代の下で、バイナリ契約は2012年から変わっていない。
  • 既存COM/ActiveX資産とWinRTは共存できる。UIの全面移行とWinRT APIの部分利用を混同しないことが、見積もりと判断の精度を守る。

OLEの記事で見た1990年代の複合ドキュメントも、公開の場へ移っていく現在のWinUIの開発も、同じ IUnknown の上にあります。Windowsの表側がどれだけ変わっても、バイナリ契約という土台は驚くほど動きません。だからこそ、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

  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

  3. 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 5 6

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

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

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

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

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

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

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

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

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

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

  14. 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

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

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

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

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

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

  20. 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

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

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

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

ブログ一覧に戻る