WinUI 3(C#)アプリから既存の C++/WinRT ライブラリ(.winmd / .dll)を呼び出したいのに、UWP のように「参照の追加」で winmd を選ぶ手段が見つからない──そんなときに使えるのが C#/WinRT(CsWinRT)です。本記事では、WinRT/C++ ライブラリを WinUI 3 から再利用するための実践的な手順と、運用上の落とし穴・設計パターンを具体例付きで詳しく解説します。
WinUI 3(C#)から WinRT/C++ ライブラリを使いたいシナリオ
前提となるシナリオを整理しておきます。
- Visual Studio 2022 で作成した WinUI 3(C#)デスクトップ アプリ がある。
- 既に別途開発済みの WinRT/C++ ライブラリ(
xxx.dllとxxx.winmd)を、そのまま利用したい。 - UWP(.NET Native)では「参照の追加 → .winmd」を選ぶだけで利用できたが、WinUI 3(.NET 6/7/8)では同じ UI が存在しない。
このギャップは、.NET 5 以降では WinRT の組み込みサポートが削除されていることが原因です。WinRT 支援は .NET ランタイム内蔵ではなく、C#/WinRT(CsWinRT)という外部ツールキットに切り出されました。
つまり、WinUI 3(.NET 6/7/8)の世界では、.winmd をプロジェクトに直接参照追加するのではなく、CsWinRT で C# 用の「投影(projection)アセンブリ」を生成して使うのが標準的なやり方になります。
UWP と WinUI 3 の違い:なぜ .winmd を直接参照できないのか
まずは、UWP と WinUI 3 の違いを整理しておくと、CsWinRT の役割が理解しやすくなります。
| 項目 | UWP(.NET Native / .NET Framework) | WinUI 3(Windows App SDK / .NET 6+) |
|---|---|---|
| WinRT サポート | .NET ランタイムに WinRT サポートが組み込み | WinRT サポートは C#/WinRT によって提供 |
| .winmd の扱い | 「参照の追加」から .winmd を直接参照可能 | .winmd を直接参照すると NETSDK1130 エラー(.NET 5+ では非対応) |
| Windows SDK の参照 | UWP プロジェクトが自動で設定 | <TargetFramework>netX.0-windows10.0.19041.0</TargetFramework> のような TFM で指定 |
| 独自 WinRT コンポーネント | .winmd を直接参照すれば C# から利用可能 | C#/WinRT で C# 用の投影アセンブリを作成してから参照 |
特に重要なのが、.NET 6+ では「.winmd 直接参照」が明示的に禁止されている点です。そのため、WinUI 3 では UWP 時代の手順をそのまま踏襲することはできません。
C#/WinRT(CsWinRT)とは何か?
C#/WinRT(CsWinRT)は、C# 向けに WinRT API を自然な C# の型として扱えるようにする 言語プロジェクション ツールキットです。
- NuGet パッケージ名:
Microsoft.Windows.CsWinRT - 対応:.NET 6 以降(WinUI 3 も .NET 6+)
- 役割:
*.winmd(WinRT メタデータ)を入力にして、C# コード(または DLL)を生成- 生成された DLL を通常の .NET アセンブリとして参照することで、WinRT コンポーネントを C# から利用できる
Windows App SDK / WinUI 3 自体も内部的には C#/WinRT によるプロジェクションを利用しており、カスタム WinRT コンポーネントを .NET アプリから利用する際の公式な道具立てと考えて構いません。
Microsoft Q&A でも、「WinUI 3 から WinRT/C++ ライブラリを使いたい」場合の回答として、「CsWinRT NuGet を使う」と明言されています(Windows 10 22H2 で動作確認)。
どんなアーキテクチャで組むべきか
WinUI 3(C#)から WinRT/C++ ライブラリを使う構成は、主に次の 3 パターンに整理できます。
| パターン | 概要 | メリット | 向いているケース |
|---|---|---|---|
| 1. 公式 / サードパーティ製 NuGet 投影あり | ライブラリ提供側が C#/WinRT 投影 DLL を含む NuGetを提供 | アプリ側は NuGet を入れるだけ。設定が最小。競合も起こりにくい。 | 市販コンポーネントや OSS ライブラリなど |
| 2. 自前で C# 投影ライブラリを作る(推奨) | C# クラスライブラリを 1 本作り、そこで CsWinRT を実行して投影 DLL(interop DLL)を生成 | WinRT → C# の変換ロジックをアプリから分離でき、再利用・テストがしやすい。設計がきれい。 | 自社 C++/WinRT コンポーネントを複数アプリから使い回したい場合 |
| 3. WinUI 3 アプリ自身で投影を生成 | WinUI 3 プロジェクトにそのまま CsWinRT 設定を追加して投影コードを生成 | プロジェクト数は増えない。小規模 PoC で手っ取り早い。 | 検証用・サンプル・単一アプリ専用の小さなコンポーネント |
Microsoft のドキュメントでは 「C++/WinRT コンポーネントとは別に C# の projection プロジェクトを用意する」構成が推奨されています。
本記事では、実戦投入しやすい「パターン 2:自前投影ライブラリ」を軸に解説し、その後で簡易な「パターン 3」も紹介します。
前提:WinRT/C++ ライブラリ側で確認すべきポイント
まず、手元の C++ ライブラリが 本当に「WinRT コンポーネント」になっているかを確認します。
- .winmd ファイルが存在するか
MyLibrary.dllと同じフォルダにMyLibrary.winmdがあること。- 無い場合、それは単なるネイティブ DLL の可能性が高く、CsWinRT の対象外です。
- Visual Studio のプロジェクト種別
Windows Runtime Component (C++/WinRT)やWindows Runtime Component(WinUI 3)テンプレートから作られているか。
- Windows Desktop Compatible が有効か(C++/WinRT のプロジェクト設定)
- Project Properties > Configuration Properties > General > Windows Desktop Compatible を Yes に設定することで、デスクトップアプリで正しくロードできるようになります。
もし .winmd が無い/WinRT コンポーネントでない場合、CsWinRT ではなく P/Invoke や C++/CLI ラッパーを検討する必要があります(後述)。
WinUI 3 プロジェクト側の下準備
ターゲットフレームワーク(TFM)の設定
WinUI 3 アプリの .csproj で、TFM に Windows バージョン付きの TFM を指定しておきます。例:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWinUI>true</UseWinUI>
</PropertyGroup>
...
</Project>
この形式の TFM を指定すると、Windows SDK の WinRT API 投影が自動で参照に追加され、WinUI 3 + Windows App SDK の環境でも WinRT API を利用できるようになります。
CsWinRT NuGet パッケージのインストール
次に、C# 投影を生成するプロジェクト(後述の C# クラスライブラリ)に Microsoft.Windows.CsWinRT をインストールします。
- ソリューション エクスプローラーで対象プロジェクトを右クリック → [NuGet パッケージの管理]。
- [参照] タブで
Microsoft.Windows.CsWinRTを検索し、最新の安定版をインストール。
これにより、ビルド時に cswinrt.exe が実行され、指定した .winmd から C# のコード/DLL を生成するビルドターゲットが組み込まれます。
推奨パターン:C# 投影ライブラリ(クラス ライブラリ)を作る
ここからは、次のような構成を想定して手順を説明します。
- NativeLibrary.WinRT:C++/WinRT の Windows Runtime Component(既存)
- NativeLibrary.Projection:C# クラス ライブラリ(CsWinRT を使って C# 投影 DLL を生成)
- MyWinUIApp:WinUI 3 C# アプリ(
NativeLibrary.Projection.dllを参照して利用)
1. C# クラスライブラリ(投影プロジェクト)を作成
- ソリューションを右クリック → [追加] → [新しいプロジェクト]。
- 「クラス ライブラリ(C# / Windows)」テンプレートを選択し、プロジェクト名を
NativeLibrary.Projectionとする。 - .NET のバージョンは .NET 6 以上を指定。
生成された NativeLibrary.Projection.csproj を開き、TFM を Windows 付きに変更します。
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<Platform>AnyCPU</Platform>
</PropertyGroup>
Windows 付き TFM にすることで、WinRT の投影に必要な依存関係が揃います。
2. CsWinRT のプロジェクト設定を追加
次に、.csproj に CsWinRT 関連のプロパティを追加します。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<Platform>AnyCPU</Platform>
</PropertyGroup>
<PropertyGroup>
<!-- 投影対象とする WinRT 名前空間を指定 -->
<CsWinRTIncludes>Contoso.Widget*;Contoso.Common*</CsWinRTIncludes>
<!-- 生成された C# ソースを出力するフォルダ(ここでは bin 配下) -->
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
</ItemGroup>
</Project>
CsWinRTIncludes・CsWinRTGeneratedFilesDir などのプロパティは CsWinRT の NuGet ドキュメントで公式に説明されており、投影対象の名前空間と生成ファイルの出力先などを制御します。
3. CsWinRTInputs で .winmd を登録する
次に、投影の入力となる .winmd ファイルを CsWinRT に教えます。
- C++/WinRT プロジェクトがソリューション内にある場合:
<ItemGroup>
<ProjectReference Include="..\NativeLibrary.WinRT\NativeLibrary.WinRT.vcxproj" />
<!-- C++/WinRT のビルド成果物(.winmd)を CsWinRT の入力に指定 -->
<CsWinRTInputs Include="..\_build\x64\Release\NativeLibrary.WinRT\bin\NativeLibrary.WinRT.winmd" />
</ItemGroup>
- 既にビルド済みの
xxx.winmdを持っている場合:
<ItemGroup>
<CsWinRTInputs Include="..\ExternalLibs\MyLibrary.winmd" />
</ItemGroup>
CsWinRTInputs は「どの .winmd からメタデータを読み取るか」を指定するプロパティで、既定では @(ReferencePath) が対象ですが、外部コンポーネントを利用する場合は明示指定しておくと安全です。
4. ビルドして C# 投影 DLL を生成
ここまで設定したら、投影プロジェクトをビルドします。
- ビルド後、
bin\Release\net8.0-windows10.0.19041.0付近に 投影 DLL(例:NativeLibrary.Projection.dll)が生成されます。 - 同時に C++ 側の
NativeLibrary.WinRT.dllも(プロジェクト参照していれば)ビルドされます。
この投影 DLL は、WinRT 型を C# から扱いやすくするための インタープリタ/アダプタの役割を果たします。
5. WinUI 3 アプリから投影 DLL を参照する
最後に、WinUI 3 アプリ(MyWinUIApp)から 投影 DLL を通常の .NET アセンブリとして参照します。
- MyWinUIApp プロジェクトを右クリック → [追加] → [プロジェクト参照]。
NativeLibrary.Projectionプロジェクト(または生成済み DLL)を参照に追加。
参照が済めば、C# コードから using Contoso.Widget; のように名前空間をインポートし、通常の .NET クラスのように利用できます。
using Contoso.Widget;
namespace MyWinUIApp.ViewModels;
public class MainViewModel
{
public string VersionText { get; }
public MainViewModel()
{
// WinRT/C++ コンポーネントのクラス(例)
var info = new LibraryInfo();
VersionText = info.GetVersionString();
}
}
ここで LibraryInfo は元々 C++/WinRT のランタイム クラスとして実装されているものですが、C# 側からは純粋なクラスのように扱えます。
簡易版:WinUI 3 アプリ自身で投影を生成する方法
プロジェクト数を増やしたくない小規模アプリなら、WinUI 3 アプリ自身に CsWinRT 設定を追加して投影を生成することも可能です。
WinUI 3 の .csproj に、先ほどと同様に CsWinRTIncludes・CsWinRTInputs を追加します。
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWinUI>true</UseWinUI>
<CsWinRTIncludes>Contoso.Widget*</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(IntermediateOutputPath)\GeneratedProjection</CsWinRTGeneratedFilesDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<CsWinRTInputs Include="..\ExternalLibs\MyLibrary.winmd" />
</ItemGroup>
ビルドすると、WinUI 3 プロジェクト内に投影コードが生成され、そのまま同一プロジェクト内で使用できます。
ただし、CsWinRT の issue では、アプリ本体で投影生成を行う構成は、コンポーネント側の変更が正しく反映されないなどの不具合報告もあるため、長期的には 専用の投影プロジェクト(クラスライブラリ)を分ける構成の方が無難です。
DLL の配置と配布:MSIX とアンパッケージド
CsWinRT で C# 投影を生成できても、実装 DLL(C++/WinRT の .dll)が見つからなければランタイムエラーになります。配布形態ごとの配置パターンを整理します。
| アプリ形態 | 配置場所 | ポイント |
|---|---|---|
| MSIX(パッケージド WinUI 3) | パッケージ プロジェクト(.wapproj)のコンテンツとしてNativeLibrary.WinRT.dll を追加 | 「出力ディレクトリへコピー」設定で、実行時にパッケージ内からロードされるようにする。 |
| アンパッケージド WinUI 3 | MyWinUIApp.exe と同じフォルダ、またはサブフォルダ | 通常は EXE と同じフォルダに置く。複数アーキテクチャを配布する場合は RID ごとにフォルダを分けて管理。 |
| NuGet で配布する WinRT ライブラリ | NuGet パッケージ内の runtimes\win10-x64\native\xxx.dll など | 投影 DLL は lib\net6.0-windows... に、実装 DLL は runtimes\win*-x64\native に入れるのが一般的。 |
特に、アプリと DLL のアーキテクチャ(x64 / x86 / ARM64)が一致していないと、読み込みに失敗します。C++/WinRT コンポーネントはアーキテクチャ別にビルドし、アプリのプラットフォーム設定と揃えてください。
よくあるエラーとトラブルシューティング
NETSDK1130:.winmd を直接参照したときのエラー
.NET 6+ のプロジェクトで .winmd を直接「参照の追加」しようとすると、以下のようなエラーが出ることがあります。
error NETSDK1130: Windows Metadata component cannot be referenced.
Referencing a Windows Metadata component directly when targeting .NET 5 or higher is not supported.
対処:
- .winmd を直接参照するのをやめ、CsWinRT を使って C# 投影アセンブリを生成する。
- 可能であれば、ライブラリ提供元の 公式 NuGet パッケージ(既に投影 DLL を含む)がないか確認する。
FileNotFoundException / TypeLoadException / ActivationException
実行時に次のような症状が出ることがあります。
- 特定の WinRT クラスを new しようとした瞬間に例外
- 「型が見つからない」「アクティベーションできない」といったエラー
チェックすべきポイント:
- 名前空間フィルタの漏れ
CsWinRTIncludesに対象の名前空間(例:Contoso.Widget*)が含まれているか。- サブ名前空間まで必要なら
Contoso.*と広めに指定してみる。
- DLL 配置ミス
- 実装 DLL(
xxx.dll)が出力フォルダ/MSIX にきちんと含まれているか。 - アーキテクチャがアプリと一致しているか。
- 実装 DLL(
- Windows App SDK / WinUI 3 の依存関係
- WinRT コンポーネントが WinUI 3 の型を参照している場合、投影プロジェクト側にも Windows App SDK の NuGet 参照が必要になることがあります。
MSB3271:プロセッサ アーキテクチャの不一致
投影プロジェクトをビルドしたときに、次のような警告/エラーが出るケースがあります。
There was a mismatch between the processor architecture of the project being built "MSIL"
and the processor architecture "x86" of the implementation file "..\MyComponent.dll".
基本的な対処:
- 投影プロジェクト側の
Platformを AnyCPU にする。 - C++ 側の DLL をアプリと同じアーキテクチャ(x64 など)でビルドする。
最終手段として、ドキュメントでは以下のように警告を抑制するプロパティも紹介されていますが、これは根本解決ではありません。
<PropertyGroup>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
CsWinRT 運用のコツとベストプラクティス
1. まずは「公式投影 NuGet」がないか確認する
C#/WinRT のドキュメントでは、「同じ WinRT 型に対して複数のプロジェクションを生成すると競合する可能性がある」ため、可能なら 提供元が用意した投影アセンブリ(NuGet)をそのまま使うことが推奨されています。
- まずは
MyLibraryという名前で NuGet を検索。 - 説明文に
C#/WinRTやprojectionといったキーワードがあれば、そのパッケージだけで完結する可能性が高いです。
2. 名前空間フィルタ(CsWinRTIncludes)は狭めすぎない
CsWinRT は CsWinRTIncludes などで投影対象の名前空間を絞り込めますが、最初は広め(Contoso.* など)に設定しておき、ビルドに成功することを優先する方がトラブルが少なくなります。
- 最初:
CsWinRTIncludes>Contoso.*</CsWinRTIncludes - 運用フェーズ:不要な型が多すぎるときに
Contoso.Widget*などに絞る
3. ビルド出力をソースとは別フォルダに分ける
Microsoft のサンプルでは、Directory.Build.props でビルド出力を _build フォルダにまとめる構成になっています。これにより、
- 生成 C# ファイルを誤ってソース管理に含めてしまうリスクを減らせる
- 複数構成(Debug/Release、x86/x64)での重複定義エラーを避けやすい
といった効果が期待できます。
4. 複数の C++/WinRT コンポーネントがある場合は構成をシンプルに保つ
CsWinRT の issue やコミュニティ投稿では、複数の C++/WinRT コンポーネントや複雑な構成で投影を生成すると、ビルドキャッシュや投影更新の不整合が起こるケースも報告されています。
そのため、
- 共通の投影プロジェクトを 1 つ用意し、そこにすべての .winmd を集約する
- アプリ側ではその投影 DLL だけを参照する
という構成にまとめておくと、将来的な保守が楽になります。
CsWinRT 以外の選択肢との比較
最後に、「そもそも WinRT である必要があるか?」という観点から、他の連携手段と比較してみます。
| 方式 | 対象ライブラリ | メリット | デメリット / 制約 |
|---|---|---|---|
| CsWinRT(C#/WinRT) | WinRT コンポーネント(.winmd + .dll) | 型安全・非同期サポート・COM インターフェースなど、WinRT の強みをそのまま利用可能。 | プロジェクション生成や DLL 配置など、初期設定はやや複雑。 |
| P/Invoke | 純粋なネイティブ DLL(C API) | WinRT でない既存 C/C++ の再利用に広く使える。仕組みが単純。 | 構造体のマーシャリングや所有権管理をすべて手で書く必要がある。 |
| C++/CLI ラッパー | ネイティブ DLL(C++ オブジェクト) | 複雑なオブジェクト指向 API でも比較的素直に橋渡しできる。 | フル .NET Framework では有力だが、.NET 6+ / WinUI 3 の世界では採用ハードルが高い。 |
| プロセス間通信(gRPC / Named Pipe など) | 独立プロセスとして動くサービス | デプロイ境界を分離できる。クラッシュの影響範囲も分けられる。 | レイテンシやデータシリアライズのオーバーヘッドが増える。 |
既に WinRT コンポーネントとして整理された C++ 資産があるのであれば、WinUI 3 からは CsWinRT を使ってそのまま再利用するのが自然な選択です。一方、まだライブラリ設計の段階であれば、「そもそも WinRT にするのか、純粋なネイティブ DLL + P/Invoke にするのか」をあらかじめ検討しておく価値があります。
まとめ:WinRT/C++ 資産を WinUI 3 で賢く再利用する
本記事のポイントを整理すると、次のようになります。
- .NET 6+ / WinUI 3 の世界では、.winmd を直接参照することはできない(NETSDK1130)。
- 代わりに C#/WinRT(CsWinRT) を利用し、WinRT/C++ コンポーネントから C# 投影アセンブリを生成して参照する。
- 実践的には、C# クラスライブラリを 1 本用意して投影を生成し、WinUI 3 アプリはその DLL を参照する構成が扱いやすく、Microsoft ドキュメントもこのパターンを前提にしている。
- 実装 DLL の配置(MSIX / アンパッケージド)とアーキテクチャを正しく揃えることが、実行時エラー回避のカギ。
- ライブラリ提供側が既に C#/WinRT 投影 NuGet を用意している場合は、それをそのまま利用するのが最も安全。
一度構成とビルドパイプラインさえ整えてしまえば、WinUI 3 から既存の C++/WinRT 資産を高パフォーマンスかつ型安全に再利用できます。将来の拡張や別アプリへの転用も見据えつつ、「WinRT コンポーネント → CsWinRT 投影 → WinUI 3 アプリ」というパターンを、自分のプロジェクトに合わせてテンプレート化しておくのがおすすめです。

コメント