「トースト通知を出したいだけなのに、ピッカーを呼んだら例外で落ちた」「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を要求し、IInspectableはIUnknownを要求します。QueryInterface・AddRef・Releaseは今もすべての呼び出しの土台です。3 IInspectableが足すのはGetIids・GetRuntimeClassName・GetTrustLevelの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)」と明記しています。開発体制が変わっても、足元の契約は
IUnknown→IInspectableのまま変わっていません。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)は世代ごとに対応と制約が異なります。
flowchart LR
accTitle: WinRTとCOMの知識マップ
accDescr: 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["WinRT(Windows Runtime)"]
com["COM(コンポーネントオブジェクトモデル)"]
iinspectable["IInspectable"]
iunknown["IUnknown"]
winmd["Windows Metadata(.winmd)"]
ecma_335["ECMA-335"]
language_projection["言語プロジェクション"]
cpp_winrt["C++/WinRT"]
cs_winrt["C#/WinRT(CsWinRT)"]
dotnet_builtin_winrt_support[".NET組み込みWinRTサポート"]
winui["WinUI(Windows App SDK)"]
windows_app_sdk["Windows App SDK"]
core_window_dependent_ui["CoreWindow依存のWinRT UI"]
initialize_with_window["IInitializeWithWindow"]
hwnd["HWND(ウィンドウハンドル)"]
data_transfer_manager_interop["IDataTransferManagerInterop"]
package_identity_required_winrt_api["package identity必須のWinRT API"]
package_identity["package identity"]
msix_packaging["MSIXパッケージング(packaged)"]
sparse_package["外部の場所を指すpackaged(sparse package)"]
ro_initialize["RoInitialize"]
com_apartment_model["COMアパートメントモデル(STA/MTA)"]
gradual_winui_migration["既存WPF/WinFormsへのWinUI段階移行"]
xaml_islands["XAML Islands"]
winrt -->|"前提とする"| com
winrt -->|"前提とする"| iinspectable
iinspectable -->|"前提とする"| iunknown
winrt -->|"利用する"| winmd
winmd -->|"利用する"| ecma_335
language_projection -->|"利用する"| winmd
cpp_winrt -->|"実装を担う"| language_projection
cs_winrt -->|"実装を担う"| language_projection
cs_winrt -->|"の後継"| dotnet_builtin_winrt_support
winui -->|"前提とする"| windows_app_sdk
winui -->|"前提とする"| winrt
core_window_dependent_ui -.->|"前提とする"| initialize_with_window
initialize_with_window -->|"利用する"| hwnd
core_window_dependent_ui -.->|"前提とする"| data_transfer_manager_interop
data_transfer_manager_interop -->|"利用する"| hwnd
package_identity_required_winrt_api -->|"前提とする"| package_identity
package_identity -->|"で構成できる"| msix_packaging
package_identity -->|"で構成できる"| sparse_package
ro_initialize -->|"利用する"| com_apartment_model
gradual_winui_migration -.->|"利用する"| 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 から派生し、その IInspectable が IUnknown から派生する、という一段の積み増しです。1 参照カウントもQueryInterfaceもHRESULTも、そのまま生きています。C++/WinRTの入門ドキュメントはさらに率直で、WinRT APIを「COMの進化(an evolution of COM)」と呼び、「Windows RuntimeはCOMに基づいており、言語プロジェクションを通じてアクセスされるように設計されている」と説明しています。215
flowchart TB
accTitle: 古典COMとWinRTの系譜
accDescr: IUnknownとvtableによるCOMのバイナリ契約という共通の土台の上に、1990年代からのOLE・ActiveXなど古典COMの世界と、2012年以降のWinRTとその上のWinUI・Windows App SDKの世界が並んで建っており、両者は排他ではなく地続きである
base["COMのバイナリ契約(IUnknown・vtable)"]
base --> classic["古典COM(OLE・ActiveX・自作COM)"]
base --> winrt["WinRT(IInspectable・.winmd)"]
winrt --> winui["WinUI/Windows App SDK"]
classic -.-> coexist["同じ土台の上で共存できる"]
winrt -.-> coexist
図1: 古典COMとWinRTは別世界ではなく、同じバイナリ契約の上の新旧2世代である。
だからこの記事は「新しいAPIの紹介」ではありません。すでに持っているCOMの知識が、2026年のWindows開発のどこにどう効くのかを確かめる記事です。
3. IUnknownの上のIInspectable
WinRTの型システムを規定する公式仕様は、インターフェースの土台をこう定めています ── すべてのWinRTインターフェースは暗黙に IInspectable を要求し、IInspectable は IUnknown を要求する。IUnknown は従来のCOMの用法どおり QueryInterface・AddRef・Release の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
flowchart TB
accTitle: IUnknownの上に乗るIInspectable
accDescr: すべてのWinRTインターフェースはIInspectableを要求し、IInspectableはIUnknownを要求する。IUnknownがQueryInterface・AddRef・Releaseを、IInspectableがGetIids・GetRuntimeClassName・GetTrustLevelを提供し、その上に各WinRTインターフェースのメソッドが乗る
unk["IUnknown(QI・AddRef・Release)"]
insp["IInspectable(GetIids・型名・信頼レベル)"]
api["各WinRTインターフェースのメソッド"]
unk --> insp
insp --> api
図2: WinRTのオブジェクトはIUnknownの3メソッドの上にIInspectableの3メソッドを積み、その上に個々のAPIが乗る。
flowchart TB
accTitle: GetRuntimeClassNameから言語プロジェクションへ
accDescr: 呼び出し側がオブジェクトのGetRuntimeClassNameを呼ぶと完全修飾のWinRT型名が返り、その型名をWindows Metadataで解決すると型の完全な定義が得られ、これが各言語への投影を可能にする
obj["WinRTオブジェクト"] --> name["型名(GetRuntimeClassName)"]
name --> md[".winmdで型定義を解決"]
md --> proj["言語プロジェクションが可能に"]
図3: 「実行時に型名が取れて、型名からメタデータが引ける」ことがWinRTの仕掛けの中心にある。
もうひとつ、COMに慣れた人ほど気づく違いがあります。WinRTの型システムには、ユーザー定義インターフェース同士の継承がありません。古典COMでは IFileSystemBindData2 : IFileSystemBindData のような派生が普通でしたが、WinRTはこれを意図的に持たず、代わりに「インターフェースAはインターフェースBを要求する(requires)」という宣言で表現します(ここまで見た IUnknown → IInspectable という基底の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 を全言語のプロジェクションが読む形に共通化されました。
flowchart TB
accTitle: WinRTコンポーネントの制作パイプライン
accDescr: WinRTコンポーネントの契約は今もIDLすなわちMIDL 3.0で記述し、MIDLコンパイラがそれを.winmdにコンパイルする。配布されるのはこの.winmdで、cppwinrt.exeやcswinrt.exeなど各言語のプロジェクションがこれを読んで投影を生成する。置き換わったのはIDLではなく配布される型情報の形である
idl2["契約を記述(IDL・MIDL 3.0)"]
midl2["MIDLコンパイラ"]
winmd4[".winmd(配布される型情報)"]
proj3["各言語の投影を生成"]
idl2 --> midl2
midl2 --> winmd4
winmd4 --> proj3
図4: 契約の入り口(IDL)は現役のまま、配布される型情報の出口が.winmdに統一された。
最後の点が「WinRT=マネージド」という誤解の出どころです。.winmd をツールで開くと.NETアセンブリのように見えます。しかしそれは形式を借りただけで、中身は「COMインターフェースの契約書」です。システム提供のWinRT APIでは、実装はOSのネイティブDLLにあり、実行にCLRは要りません。なお、これはシステムAPIに限った話です ── サードパーティのWinRTコンポーネントの .winmd は実装コードを含むこともあり、特にマネージドな(C#で書かれた)コンポーネントの .winmd はMSILを含むため、その実行には対応する.NETランタイムが必要です。5
flowchart TB
accTitle: .winmdと実装の分離
accDescr: .winmdはECMA-335の物理形式を借りたメタデータで、システム提供のものは実行コードを含まない契約書であり、システム提供のWinRT APIの実装はOSのネイティブDLLにある。この分離のため.winmdが.NETアセンブリのように見えてもシステムWinRT APIの実行にCLRは不要である(サードパーティのマネージドなコンポーネントの.winmdはMSILを含み.NETランタイムを要する)
winmd3[".winmd(契約書・ECMA-335形式)"]
impl["OSのネイティブDLL(実装)"]
winmd3 -.->|"システム提供はコード無し"| note3["システムAPIはCLR不要"]
impl --> note3
winmd3 ---|"型定義と実装が対応"| impl
図5: システム提供のWinRT APIでは.winmdは契約書、実装はOSのネイティブDLL。形式が.NET風でも実行はネイティブのCOM。
flowchart TB
accTitle: 古典COMの型情報と.winmdの対比
accDescr: 古典COMではIDLからC++ヘッダー、型ライブラリからVB6やスクリプト、相互運用アセンブリから.NETと、言語ごとに型情報の経路が分かれてばらつきの原因になっていたのに対し、WinRTでは単一の.winmdを全言語のプロジェクションが共通に読む
subgraph old["古典COM:経路が言語ごと"]
idl["IDL→C++ヘッダー"]
tlb["TLB→VB6・スクリプト"]
ia["相互運用アセンブリ→.NET"]
end
winmd[".winmd(単一のメタデータ)"]
winmd --> all["全言語のプロジェクションが共通に読む"]
図6: 言語ごとにばらけていた型情報の配り方を、.winmdは「1つのメタデータを全員が読む」形に畳み直した。
古典COMを知っている人向けに一言でまとめると、.winmd は「型ライブラリのやり直し」です。TLBが果たそうとした役割を、ECMA-335という実績ある形式の上で、最初から全言語共通の正本として設計し直したものと捉えると、位置づけがすっきりします。ただし、制約が無くなったわけではありません。オートメーション向けに偏っていたTLBの制約が、「全言語へ安全に投影できること」を軸にしたWinRT独自の型システムの制約 ── 3章で見たユーザー定義インターフェース継承の不在はその一例です ── に置き換わった、というのが正確な見立てです。3 したがって、既存のCOM/IDL契約が表現していたものをそのままWinRTの契約に持ち込めるとは限らず、APIの設計し直しが必要になる場合があります。
flowchart TB
accTitle: 型ライブラリの制約からWinRT型システムの制約への置き換え
accDescr: TLB(型ライブラリ)が持っていたオートメーション寄りの表現力の制約は、.winmdで取り払われたのではなく、全言語へ安全に投影できることを軸にしたWinRT独自の型システムの制約に置き換わった。その一例がユーザー定義インターフェース継承の不在で、既存のCOM/IDL契約をそのまま持ち込めるとは限らず、APIの設計し直しが必要になる場合がある
tlb["TLBの制約(オートメーション寄り)"] -->|"置き換え"| wrt["WinRT型システムの制約(投影可能性軸)"]
wrt -.-> ex["例:ユーザー定義の継承は不可"]
wrt -.-> re["既存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
flowchart TB
accTitle: .winmdから各言語への投影の生成
accDescr: 単一の.winmdをcppwinrt.exeが読むとC++17向けの投影ヘッダーが、cswinrt.exeが読むとC#向けの相互運用アセンブリが生成され、それぞれの言語の慣用に沿った形でWinRT APIが公開される
winmd2[".winmd(APIの契約書)"]
winmd2 --> cpp["cppwinrt.exe→C++17ヘッダー"]
winmd2 --> cs["cswinrt.exe→C#相互運用アセンブリ"]
cpp --> cppcode["C++の慣用で呼べる"]
cs --> cscode["C#の慣用で呼べる"]
図8: 投影は手書きのラッパーではなく、契約書(.winmd)からツールが機械的に生成する。
「ラッパー」と「投影」の違いは、単なる言葉の好みではありません。ラッパーは人が書いた翻訳層で、APIの追加のたびに人が追従します。投影はメタデータからの機械的な導出なので、.winmd に載っているAPIはすべて、最初から全対応言語で使えます。そしてどの言語から呼んでも、投影の下で起きていることは同じCOM呼び出しです。C#で await picker.PickSingleFolderAsync() と書いたとき、投影が IAsyncOperation というWinRTインターフェースを.NETの Task の世界に橋渡ししていますが、ワイヤの上を流れるのはvtable経由のメソッド呼び出しと HRESULT です。エラーがCOM例外(0x80070005 のようなHRESULT)の顔で飛んでくるのは、このためです。
flowchart TB
accTitle: C#コードからOSのWinRT APIまでの層
accDescr: アプリのC#やC++のコードは言語プロジェクションを通じてWinRTのABIすなわちIInspectableのvtable呼び出しに変換され、OSが実装するWinRT APIに届く。投影がCOMの詳細を隠しているだけで、呼び出しの実体はCOMである
code["アプリのコード(C#・C++)"]
proj2["言語プロジェクション"]
abi["WinRT ABI(IInspectableのvtable)"]
os["OSのWinRT API実装"]
code --> proj2
proj2 --> abi
abi --> os
図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
sequenceDiagram
accTitle: デスクトップアプリからピッカーを表示する手順
accDescr: デスクトップアプリがピッカーを生成した後、そのままPickSingleFolderAsyncを呼ぶと例外や沈黙の失敗になるため、先にオーナーウィンドウのHWNDを取得しIInitializeWithWindowのInitializeで渡してから表示する必要がある
participant A as デスクトップアプリ
participant P as ピッカー(WinRT)
A->>P: 生成
alt HWNDを渡さず表示
A->>P: PickSingleFolderAsync
P-->>A: 例外または沈黙の失敗
else 先にHWNDを渡す
A->>P: IInitializeWithWindowでHWNDを設定
A->>P: PickSingleFolderAsync
P-->>A: ピッカーが表示される
end
図10: 「デスクトップにCoreWindowはない」ことを、HWNDの明示的な受け渡しで埋めるのが公式の作法。
詰まりどころ2: package identityが必要なAPI。トースト通知の履歴(ToastNotificationHistory)、ジャンプリスト、共有ターゲットなど、一部のWinRT APIはpackage identityを持つアプリ(パッケージ化されたアプリ)でしか動きません。10 従来型のインストーラーで配るunpackagedなアプリから呼ぶと失敗します。対処は、MSIXでパッケージ化するか、既存インストーラーを保ったまま識別子だけを付与する「外部の場所を指すパッケージ(いわゆるsparse package)」を使うかです。21 どのAPIがidentityを要求するかは公式の一覧で確認できます。10
flowchart TB
accTitle: package identityが必要なAPIへの2つの経路
accDescr: package identity必須のWinRT APIをunpackagedなアプリから呼ぶと失敗するため、MSIXでパッケージ化するか、既存インストーラーを維持したまま識別子だけを付与する外部の場所を指すパッケージのどちらかでpackage identityを持たせる
need["identity必須のAPIを使いたい"]
need --> m1["MSIXでパッケージ化"]
need --> m2["外部の場所を指すパッケージ"]
m1 --> id2["package identityを獲得"]
m2 --> id2
id2 -.-> okid["通知履歴などが動く"]
図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の利用でもそのまま生きています。
flowchart TB
accTitle: デスクトップからWinRT APIを呼ぶときの詰まりどころの分岐
accDescr: まずスレッドがWinRT用に初期化されていることを確認し(ネイティブコードではRoInitializeなどでSTA/MTAを明示的に指定する。C#では通常ランタイム側が処理する)、そのうえで、呼びたいWinRT APIがCoreWindow前提のUI系ならIInitializeWithWindowでHWNDを渡すか新ピッカーを使い、package identity必須ならMSIXまたは外部の場所を指すパッケージで識別子を付与し、CoreWindowやApplicationView自体に依存するAPIはデスクトップでは使えないため代替APIを探し、それ以外の大半のAPIはTFMやC++/WinRTの設定だけでそのまま呼べる
pre["スレッド初期化"] --> q{"呼びたいAPIの性質は?"}
pre -.-> auto["ネイティブは明示指定、C#は通常自動"]
q -->|"UI系"| h["HWNDか新ピッカー"]
q -->|"identity必須"| p2["MSIXか識別子付与"]
q -->|"CoreWindow自体に依存"| x["代替APIを探す"]
q -->|"それ以外"| ok3["そのまま呼べる"]
図12: スレッド初期化(ネイティブコードでは明示的に、C#では通常ランタイム任せ)を前提として、詰まりどころは3系統に分類でき、それぞれ定型の対処がある。
flowchart TB
accTitle: COMとWinRTのスレッド初期化の対応
accDescr: 古典COMではCoInitializeExでSTAまたはMTAを指定してスレッドを初期化するのに対し、WinRTではRoInitializeで同じくSTAまたはMTAの並行性モデルを指定して初期化する。CoInitializeのドキュメントもWinRT利用時はRoInitializeを呼ぶよう案内しており、アパートメントの考え方は共通である
com3["古典COM:CoInitializeEx"] --> apt["アパートメント(STA/MTA)"]
wrt["WinRT:RoInitialize"] --> apt
apt -.-> rule["UIスレッドは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のトラブルシューティングでそのまま通用します。
flowchart TB
accTitle: UIフレームワークの世代交代と変わらない土台
accDescr: WinForms・WPF・UWPのXAML・WinUIとUIフレームワークは世代交代を重ねてきたが、WinFormsとWPFはWin32とCOMの土台に直接乗り、UWPのXAMLとWinUIはWinRT ABIを介して同じWin32とCOMの土台に乗る。世代交代してきたのは上の層で、足元の契約は変わっていない
gen["UIフレームワークの世代交代"]
gen --> f1["WinForms・WPF"]
gen --> f2["UWPのXAML"]
gen --> f3["WinUI(現在)"]
f2 --> abi3["WinRT ABI(2012年〜)"]
f3 --> abi3
f1 --> stable["変わらない土台(Win32+COM)"]
abi3 --> stable
図14: 浮き沈みしてきたのはフレームワークの層。WinForms・WPFはWin32+COMに直接、UWP・WinUIはWinRT ABIを介して、同じ土台に乗っている。
flowchart TB
accTitle: WinUIアプリを支える層
accDescr: WinUIのXAMLとコントロールはWindows App SDKの一部として提供され、WinRTのABIすなわちIInspectableの契約の上で動き、さらにその下はCOMとWin32のHWNDという土台である。UIフレームワークの世代交代の下で、この足元の契約は変わっていない
ui["WinUI(XAML・コントロール)"]
sdk["Windows App SDK"]
abi2["WinRT ABI(IInspectable)"]
base2["COM+Win32(HWND)"]
ui --> sdk
sdk --> abi2
abi2 --> base2
図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_ptr、winrt::implements)で扱えます。21
第二に、「UIをWinUIへ全面移行するか」と「WinRT APIを必要な箇所だけ使うか」は、規模も期間もリスクも桁が違う別の判断です。前者はフレームワーク選定の問題で、画面資産・サードパーティコントロール・開発体制に依存します(WinForms/WPF/WinUIの選び方で扱いました)。後者は今日からできる小さな改善です。トースト通知が欲しいだけの案件でUI全面移行を見積もる必要はありませんし、逆に「WinUIに移行しないから」という理由でWinRT APIの利用まで見送る必要もありません。
flowchart TB
accTitle: 全面移行と部分利用は別の判断
accDescr: WinUIへのUI全面移行はフレームワーク選定の大きな判断で画面資産や体制に依存するのに対し、WinRT APIの部分利用はTFM設定などで既存のWPFやWinFormsアプリに今日から足せる小さな判断であり、この2つを混同せずに分けて検討する
goal["「新しいWindowsの機能を使いたい」"]
goal --> big["UI全面移行(大きな判断)"]
goal --> small["WinRT API部分利用(小さな判断)"]
big -.-> dep["画面資産・体制に依存"]
small -.-> today["既存アプリに今日から足せる"]
図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 「ビルドは通るのに通知が出ない」ときは、呼び出し側ではなく、登録側と実行文脈(パッケージ形態・昇格の有無)を疑ってください。この登録まわりの実装の詳細はトレイ常駐と通知の実装ガイドで扱っています。
flowchart TB
accTitle: トースト通知の2つの経路と必要な登録
accDescr: デスクトップアプリからトースト通知を出すには、従来のToastNotificationManager経路ではpackagedかどうかで分岐し、unpackagedアプリはAppUserModelIDを割り当てたスタートメニューショートカットの登録を経る必要があり、packagedアプリはパッケージのidentityがAppUserModelIDを与える。Windows App SDKのAppNotificationManager経路ではSDKの導入と起動時のRegister呼び出しに加えて、unpackagedアプリは配布先の各PCへのランタイム配備を、MSIXでパッケージ化したアプリはマニフェストへのCOMアクティベーター宣言を満たしてはじめて先へ進める。さらにWindows App SDK経路には昇格プロセスかどうかの分岐があり、管理者に昇格したプロセスからの通知は未サポートでShowは例外を出さず静かに失敗するため、この経路で通知が表示されるのは非昇格プロセスの場合に限られ、昇格が必要なアプリは通知を非昇格プロセスに分離する
want["トーストを出したい"]
want --> c1["従来:ToastNotificationManager"]
want --> c2["WASDK:AppNotificationManager"]
c1 --> q1{"packaged?"}
q1 -->|"いいえ"| s1["AUMIDショートカット登録"]
q1 -->|"はい"| s2["identityがAUMIDを提供"]
c2 --> r2["SDK導入+Register()"]
r2 --> q2{"packaged?"}
q2 -->|"いいえ"| s3["ランタイム配備"]
q2 -->|"はい"| s4["マニフェストにCOM宣言"]
s1 --> shown["通知が表示される"]
s2 --> shown
s3 --> elev{"昇格プロセス?"}
s4 --> elev
elev -->|"いいえ"| shown
elev -->|"はい"| fail["未サポート:Showが静かに失敗"]
fail -.-> comp["通知は非昇格プロセスに分離"]
図17: どちらの経路も、packaging形態に応じた前提を満たしてはじめて表示に到達できる。Windows App SDK経路は、前提がすべて揃っても昇格プロセスからは表示されない。
flowchart TB
accTitle: 既存資産とWinRTの付き合い方の判断フロー
accDescr: 既存のデスクトップアプリを起点に、新しいWindows機能が必要なければ残す、機能が必要ならWinRT APIの部分利用で包む(その際は6章のHWND・package identity・スレッド初期化の詰まりどころに注意)、UIそのものの刷新が必要な場合に限ってWinUIへの置き換えを検討する、という段階的な判断の流れ
start["既存デスクトップアプリ"] --> q2{"何が必要?"}
q2 -->|"現状で足りる"| keep2["残す(そのまま保守)"]
q2 -->|"新しい機能"| wrap2["包む(WinRT API部分利用)"]
q2 -->|"UIの刷新"| rep["置き換え(WinUIを評価)"]
wrap2 -.-> note2["HWND・identity・初期化に注意(6章)"]
図18: ActiveXの判断表と同じ「残す・包む・置き換える」の構図が、WinRT側にもそのまま当てはまる。
10. まとめ
- WinRTはマネージドランタイムではない。COMにメタデータ(.winmd)と言語プロジェクションを足したABIであり、Microsoft自身が「COMに基づく」「COMの進化」と明記している。
- すべてのWinRTインターフェースは
IInspectableを要求し、IInspectableはIUnknownを要求する。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 とは何か - Windows COM の設計が今でも美しい理由
- COM / ActiveX / OCX とは何か - 違いと関係をまとめて解説
- COM STA/MTA の基礎知識 - スレッドモデルとハングを避ける考え方
- ActiveX / OCX を今どう扱うか - 残す・包む・置き換える判断表
- WinForms/WPF/WinUIの選び方 - 実務判断表
- OLEオブジェクトとは何か ── 埋め込み・リンクの仕組みと業務文書の落とし穴
- .NET 8 DLLをVBAから型付きで使う方法 - COM公開とdscom TLB
関連する相談領域
合同会社小村ソフトでは、COMコンポーネント開発と、COM資産を含むWindows業務アプリの保守・改修・リプレイスを扱っています。「WPFのままトースト通知やピッカーを使いたい」「WinRT APIを呼んだら例外で止まった」「WinUIへ移行すべきか、既存資産を活かすべきか判断したい」といった、本記事の内容がそのまま論点になる段階からご相談いただけます。
参考リンク
-
Microsoft Learn, Consume COM components with C++/WinRT. COMではオブジェクトではなくインターフェースを介してプログラミングすること、それがCOMの進化(an evolution of COM)であるWinRT APIの舞台裏でも成り立つこと、winrt::com_ptrによるCOMスマートポインタでWinRTと古典COMを同じ流儀で扱えることについて。 ↩ ↩2 ↩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
-
Microsoft Learn, IInspectable interface (inspectable.h). IInspectableがすべてのWinRTクラスに必要な機能を提供すること、IUnknownを継承すること、GetIids・GetRuntimeClassName・GetTrustLevelの3メソッドを持つことについて。 ↩ ↩2
-
Microsoft Learn, Windows Metadata (WinMD) files. WinRT APIが.winmdという機械可読メタデータで記述されツールと言語プロジェクションが利用すること、Windowsがシステム提供の全WinRT APIのメタデータを同梱し解決用APIを提供すること、サードパーティも同形式で言語プロジェクションに参加できること、物理形式がECMA-335仕様(CLRアセンブリと同じ)でありシステム提供のWinMDが純粋なメタデータであることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Introduction to Microsoft Interface Definition Language 3.0. MIDL 3.0がWinRT型を宣言するための簡潔な現代的構文であること、WinRTの契約は今もIDLで記述され、MIDLコンパイラがWindowsメタデータ(.winmd)を生成することについて。 ↩ ↩2
-
Microsoft Learn, Windows Runtime (WinRT) language projections. 言語プロジェクションがWinRT APIを各言語の慣用に沿って公開すること、.winmdがWinRT APIを定義しプロジェクションがそれを読むこと、MicrosoftがサポートするのはC++/WinRT(C++17以降)とC#/WinRT(.NET)の2つであることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Built-in support for WinRT is removed from .NET. .NET 5でWindows Runtimeの組み込みサポートが.NETから削除され、CsWinRTツールチェーンに移行したことについて。 ↩ ↩2
-
Microsoft Learn, Display WinRT UI objects that depend on CoreWindow. 一部のピッカー・ポップアップ・ダイアログがCoreWindowに依存すること、CoreWindowがデスクトップアプリでサポートされないこと、IInitializeWithWindow(または同等のIDataTransferManagerInterop)を実装するクラスには表示前にオーナーウィンドウのHWNDを設定できること、WinUI 3・WPF・WinFormsのそれぞれでの手順について。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, WinRT APIs not supported in desktop apps. デスクトップアプリで使えないWinRT APIの2系統として、UWP専用UI機能に依存するAPIと、package identityを要求するAPI(ToastNotificationHistory・JumpListなど)があること、後者はMSIXでパッケージ化されたアプリでのみサポートされることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, RoInitialize function (roapi.h). RoInitializeが現在のスレッドを指定した並行性モデル(RO_INIT_SINGLETHREADED/RO_INIT_MULTITHREADED)でWindows Runtime用に初期化すること、WinRTオブジェクトを活性化・操作するすべてのスレッドが事前の初期化を必要とすること、MTAとして初期化済みのスレッドで矛盾する指定をするとRPC_E_CHANGED_MODEになることについて。 ↩ ↩2
-
GitHub, WinUI: Now Developing in the Open (microsoft/microsoft-ui-xaml Discussion #10700). 2025年7月末の公式アナウンス。WinUIのリポジトリを公開していくための段階的アプローチ(ミラー更新頻度の向上、ローカルビルドの実現、テストを整えたうえでのコミュニティ貢献の受け付け、最終的にGitHubを開発の主本拠にすること)が示されていることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, WinUI 3. WinUIが新規Windowsデスクトップアプリ向けに推奨されるネイティブUIフレームワークであること、Windows App SDKの一部として提供されること、Windows 10 バージョン1809以降で動作すること、公開の場で開発されていることについて。 ↩ ↩2 ↩3 ↩4
-
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
-
Microsoft Learn, Introduction to C++/WinRT. C++/WinRTが完全に標準の現代C++17による言語プロジェクションでありヘッダーファイルベースのライブラリとして実装されること、C++/CXとWRLの推奨後継であること、WinRTがCOM APIに基づき言語プロジェクションを通じてアクセスされるよう設計されていること、プロジェクションがCOMの詳細を隠すこと、cppwinrt.exeが.winmdから投影ヘッダーを生成することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, C#/WinRT. C#/WinRTのNuGetパッケージに含まれるcswinrt.exeが.winmdを処理してC#コードを生成し相互運用アセンブリにコンパイルすること、C++/WinRTがC++向けヘッダーを生成するのと同様の位置づけであることについて。 ↩
-
Microsoft Learn, WinRT APIs callable from a desktop app. ほとんどのWinRT APIが.NETおよびネイティブC++のデスクトップアプリから利用できること、CoreDispatcher・CoreWindow・ApplicationViewなどUWP専用に設計されたクラスが例外であることについて。 ↩ ↩2
-
Microsoft Learn, IInitializeWithWindow interface (shobjidl_core.h). デスクトップアプリで使われるWinRTオブジェクトにオーナーウィンドウを提供するためのインターフェースであること、IUnknownを継承しInitialize(HWND)メソッドを持つことについて。 ↩
-
Microsoft Learn, Use WinRT COM interop classes in .NET. ファイルピッカーやダイアログなど一部のWinRTオブジェクトがデスクトップアプリで動作する前にHWNDを必要とすること、WinRT.Interop.WindowNativeとWinRT.Interop.InitializeWithWindowという型安全なC#クラスで手書きのQueryInterface呼び出しなしに初期化できることについて。 ↩
-
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
-
Microsoft Learn, Features that require package identity. 一部のWindows機能とWinRT APIが実行時にpackage identityを要求すること、MSIXパッケージでの配布に加え、外部の場所を指すパッケージ(packaged with external location)でも識別子を得られることについて。 ↩ ↩2
-
Microsoft Learn, CoInitialize function (objbase.h). CoInitializeがCOMライブラリをSTAとして初期化すること、新しいアプリはCoInitializeExを呼ぶべきこと、Windows Runtimeを使う場合は代わりにRoInitializeまたはWindows::Foundation::Initializeを呼ばなければならないことについて。 ↩
-
Microsoft Learn, Windows App SDK. Windows App SDKがWinUIを含む現行のWindowsアプリ開発ライブラリ群であることについて。 ↩
-
Microsoft Learn, Walkthrough: WinUI 3 app with Win32 interop. WinUIのWindowクラスがデスクトップウィンドウをサポートするよう拡張されたこと、WinUI 3のデスクトップアプリではWindowがWin32のウィンドウハンドル(HWND)に裏打ちされており、ウィンドウハンドルを取得してWin32 APIで操作できることについて。 ↩
-
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
-
Microsoft Learn, DesktopWindowXamlSource Class (Microsoft.UI.Xaml.Hosting). Windows App SDKのXAMLホスティングAPIの中核クラスであり、HWNDに関連付けられた任意のUI要素にWinUIコントロールをホストできること、WPF・Windows Forms・Win32(Windows API)で作られたデスクトップアプリから利用できることについて。 ↩ ↩2
-
Microsoft Learn, Quickstart: Sending a toast notification from the desktop. デスクトップアプリからのトースト送信には、System.AppUserModel.IDを設定したスタートメニューのショートカットが前提であること、CreateToastNotifierの呼び出しにそのAppUserModelIDを渡す必要があり、無ければトーストが表示されないことについて。 ↩
-
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
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsアプリ 外注・受託開発を依頼する前に整理したいこと
Windowsアプリの外注・受託開発を依頼する前に、既存ソフト改修、装置連携、COM/ActiveX、配布・更新、保守の整理ポイントを解説します。
OLEオブジェクトとは何か ── 埋め込み・リンクの仕組みと業務文書の落とし穴
WordにExcelの表を埋め込む機能の正体がOLEオブジェクトです。埋め込みとリンクの違い、複合ファイルと構造化ストレージ、In-Place Activationの仕組みから、リンク切れ・肥大化・セキュリティ対策まで実務目線で解説します。
クリップボードとドラッグ&ドロップの仕組み ── OLEデータ転送を業務アプリで正しく扱う
Excelの表を貼ると崩れる・コピー元を閉じると貼れない――正体は同じ内容を複数形式で置くクリップボードの仕組みです。標準形式・遅延レンダリング・OLEドラッグ&ドロップから履歴とクラウド同期のポリシーまで解説します。
Windowsシェル統合の今 ── 右クリックメニュー・ファイル関連付け・Windows 11の変化
Windows 11で右クリックメニューが「その他のオプションを確認」に隠れる理由を、拡張子→ProgID→verbという関連付けの基本、従来型シェル拡張の注意点、IExplorerCommandとMSIX/sparse packageの新方式まで整理して解説します。
親が落ちたあとに何が残るか ── Job Objectで子プロセスを飼う
UIを強制終了してもSDKのヘルパーが残り、カメラやCOMポートを握ったままになるのはなぜか。Job Objectでプロセスツリーを一つの単位にし、KillOnJobCloseと完了ポートで子プロセスの寿命を設計する方法を計測アプリ目線で解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
ActiveX / 移行テーマ
COM / ActiveX / OCX を残すか、包むか、置き換えるかを整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
既存資産活用・移行支援
COM / ActiveX / OCX、32bit / 64bit 制約を抱える既存資産の活用と移行を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 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からも使えますが、便利なラッパーコントロールが無く実装負担が大きいため、現時点では慎重に評価するのが安全です。