CMake + C++を使って WinRT Component を作って C# WinUI3 アプリから使う (後編)

C++ で作ったWindows Runtime (WinRT) Component を、C# WinUI3 (.Net 6 以降) で使う方法をまとめました。

[前編](https://qiita.com/unicodon/items/17eb63890826311de132)ではWinRT Component の IDLから Windows Metadata (.winmd)を生成し、さらにその.winmdからC++コードを生成してDLLを作りました。.Net 5.0 以前ではこの.winmdとDLLを直接使うことができましたが、.Net 6 以降では WinRT Component の情報が書かれた Windows metadata (.winmd) を直接参照することができなくなりました。

直接参照するのではなく、
- C# projection のC#コードを .winmd から生成し、DLLを作る
- C# projection DLL を .winmd と WinRT component DLL とともにnuget package 化する

といった手順が必要になります。
前後編合わせた全体の流れは以下の図のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1410370/0145c17b-2e3d-58d0-3b87-d4eeaa984580.png)

本記事では以下記事をベースにこのやり方をなぞっていきます。
https://docs.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component

# C# Projection コードのプロジェクトを作る
[前編](https://qiita.com/unicodon/items/17eb63890826311de132)ではCMakeを使ってWinRT component のDLLを作りましたが、今回は Visual Studio を使って作ります。
無印の C#クラスライブラリプロジェクトテンプレートを使って新規プロジェクトを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1410370/b31ae2a2-a87b-85ec-51e6-fd8394406987.png)

プロジェクト名は```CsWinRTProjection``` にしておきます。フレームワークは .Net 6.0 を選びます。

デフォルトでClass.c1 が生成されていますが、使わないので削除します。cswinrt が生成するコードのみがビルドされることになります。

また、ターゲットフレームワークを設定します。
ソリューションエクスプローラからCsWinRTProjectionプロジェクトをダブルクリックすると、```CsWinRTProjection.csproj```のXMLが開かれるので、TargetFrameworkを以下のように```net6.0```から```net6.0-windows10.0.22000.0```に設定します。
```
	<PropertyGroup>
		<TargetFramework>net6.0-windows10.0.22000.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>
```

# Sample.winmd への参照追加
[前編](https://qiita.com/unicodon/items/17eb63890826311de132)で作成した Sample.winmd への参照を追加します。

# CsWinRT 設定

csswinrt.exe を使って .winmd ファイルからC# projection コードを生成します。

csswinrt.exe は NuGetで配布されているので、プロジェクトに追加します。```Microsoft.Windows.CsWinRT```を追加してください。

csswinrt 用の以下設定を```CsWinRTProjection.csproj```に追加します。
```
	<PropertyGroup>
		<CsWinRTIncludes>Sample</CsWinRTIncludes>
		<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
	</PropertyGroup>
```
```CsWinRTIncludes```には```Sample.idl```のnamespaceを指定します。

ここまででビルドを実行すると、 ```$(OutDir)```以下にC# projection の C#コードが生成され、それを使ってprojection DLLがビルドされます。

# NuGet 設定
つぎにNuGetまわりの設定をします。以下のようなファイルを ```nuget/Sample.nuspec``` として保存します。
```
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
	<metadata>
		<id>Sample</id>
		<version>0.1.0-prerelease</version>
		<authors>Me</authors>
		<description>A simple component</description>
		<dependencies>
			<group targetFramework="net6.0-windows10.0.22000.0" />
			<group targetFramework=".NETCoreApp3.0" />
			<group targetFramework="UAP10.0" />
			<group targetFramework=".NETFramework4.6" />
		</dependencies>
	</metadata>
	<files>
		<!--Support .NET 6, .NET Core 3, UAP, .NET Framework 4.6, C++ -->
		<!--Architecture-netural assemblies-->
		<file src="..\bin\Release\net6.0-windows10.0.22000.0\CsWinRTProjection.dll" target="lib\net6.0-windows10.0.22000.0\CsWinRTProjection.dll" />
		<file src="..\..\..\dist\bin\Sample.winmd" target="lib\netcoreapp3.0\Sample.winmd" />
		<file src="..\..\..\dist\bin\Sample.winmd" target="lib\uap10.0\Sample.winmd" />
		<file src="..\..\..\dist\bin\Sample.winmd" target="lib\net46\Sample.winmd" />
		<!--Architecture-specific implementation DLLs should be copied into RID-relative folders-->
		<file src="..\..\..\dist\bin\Sample.dll" target="runtimes\win10-x64\native\Sample.dll" />
	</files>
</package>
```
```Sample.dll```,```Sample.winmd```の場所は適宜修正します。

NuGet用に以下設定を```CsWinRTProjection.csproj```に追加します。
```
	<PropertyGroup>
		<GeneratedNugetDir>.\nuget\</GeneratedNugetDir>
		<NuspecFile>$(GeneratedNugetDir)Sample.nuspec</NuspecFile>
		<OutputPath>$(GeneratedNugetDir)</OutputPath>
		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
	</PropertyGroup>	
```

ここまででビルドすると、```nuget/Sample.0.1.0-prerelease.nupkg```が生成されます。

# C# WinUI3アプリからの参照
C# WinUI3 アプリから参照します。WinUI3アプリケーションプロジェクトを新規作成します。名前はCsWinUI3Appにしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1410370/e3892467-0da2-2195-7bba-1709df6fd04f.png)

```CsWinRTProjection``` で作ったnupkg を参照するため、```CsWinUI3App.csproj```に以下を追加します。
```
	<PropertyGroup>
		<RestoreSources>
			https://api.nuget.org/v3/index.json;
			../../../CsWinRTProjection/CsWinRTProjection/nuget
		</RestoreSources>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Sample" Version="0.1.0-prerelease" />
	</ItemGroup>
```
パスやバージョンは適宜修正します。

これでC# WinUI3 App からC++ WinRT Componentが使えるようになりました。
試しにコードを追加してみましょう。
WinUI3アプリのスケルトンにもともとあるボタンのClickハンドラに以下のコードを追加します。

```
        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            var sc = new Sample.SampleClass();
            myButton.Content = sc.sqrt(100).ToString();
        }
```

アプリを起動してボタンをクリックすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1410370/0cbaae12-a79a-73d0-c437-c7172cefee3a.png)
から
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1410370/85073d08-4db4-93f7-aa97-434ff8a359b3.png)
と変化することで、実際にC++ WinRT Component が使われていることが確認できます。

# まとめ
前回と合わせて、CMake + C++ で作成したWinRT Component を C# WinUI3 アプリから使用する方法を説明しました。
C# projection の生成もCMake で出来るようにしたかったのですが、将来の課題として残しておきます。

関連コードは https://github.com/unicodon/cmake-winui3/tree/sample-v1 に置いてあります。