[C#] C++/WinRTのブリッジを作ってC#から呼び出す方法

# はじめに

Windows 10では、UWP（ストアアプリ、ネイティブアプリ）から使えるいろいろな新しいAPI (WinRT) が追加されています。
強力な機能は魅力的ですが、UWPアプリはサンドボックス内で実行されるので、セキュリティによって実装に制約が生じたり、スタートメニューからしか起動できない、社内などの他の人への配布が大変（ストア経由でないアプリを使うためには設定が必要）と、少々使いづらい点もあります。
そこで、**Windowsの普通の（exeをダブルクリックして起動できる）デスクトップアプリから、WinRTの機能を「つまみ食い」できると便利だと思います。**

そのための仕組みとして「C++/WinRT」があります。

- [C++/WinRT の概要 - UWP applications | Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/uwp/cpp-and-winrt-apis/intro-to-using-cpp-with-winrt)
- [ネイティブの C++ から UWP の API を呼ぶ (C++/WinRT を使って) - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2018/10/16/144556)
- [C++/WinRTの始め方 - Qiita](https://qiita.com/yottui/items/390bf7582fa1abb42766)

しかし、アプリをUI部分も含めて全部ネイティブのC++で書くのは大変なので、C#のフォームアプリから呼び出せると都合がよいと思うわけです。
そこで、**C++/WinRTでブリッジのライブラリを作成し、C#からそのクラスを参照させることによって、C#からWinRTの恩恵にあずかる方法を紹介します。**

後の公式サンプルのreadmeによると、この機能が使えるのは「new feature in Windows Builds 18309+」ということなので、**Windows 10 バージョン1903以降**で使える機能ということになります。

# 開発環境

* Windows 10 Home 1903
* Visual Studio Community 2019

# 事前準備

Visual Studio Installerからコンポーネントを追加します。

- ワークロード: 以下の3つにチェック
    - .NETデスクトップ開発
    - C++によるデスクトップ開発
    - ユニバーサルWindowsプラットフォーム開発
- 個別のコンポーネント
    - Windows 10 SDK (10.0.18362.0以降を1つ以上)
    - （他にもあったかどうか忘れました…）

さらに、C++/WinRT開発のためのVSIXをインストールしておきます。
[C++/WinRT - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=CppWinRTTeam.cppwinrt101804264)

# 公式サンプルの動作確認

C++/WinRTブリッジを介したC#フォームアプリの作成方法については、Microsoftから公式サンプルが提供されています。
[microsoft/RegFree_WinRT: Sample Code showing use of Registration-free WinRT to access a 3rd party native WinRT Component from a non-packaged Desktop app](https://github.com/microsoft/RegFree_WinRT)

まずは RegFree_WinRT/CS/RegFree_WinRT.sln をビルド・実行できることを確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/5429cfda-a330-de5c-b271-c9c80f2d6928.png)

# 自分でブリッジを書く

いよいよここからが本番です。以下がMS公式のドキュメント。
[Enhancing Non-packaged Desktop Apps using Windows Runtime Components - Windows Developer Blog](https://blogs.windows.com/windowsdeveloper/2019/04/30/enhancing-non-packaged-desktop-apps-using-windows-runtime-components/)

新しいソリューションに、以下2つのプロジェクトを追加します。

- Windows Runtime Component (C++/WinRT)
    - ブリッジを書くためのプロジェクトになります。
    - 名前はとりあえずデフォルトの RuntimeComponent1
    - Windowsのターゲットバージョンは1903以降で。
- WPF アプリ (.NET Framework) または Windows フォーム アプリケーション (.NET Framework)
    - アプリのメインのプロジェクトになります。
    - 名前はとりあえずデフォルトの WpfApp1
    - 対象のフレームワークは .NET Framework 4.7.2

## C++/WinRT側

最初に何も変更せずに一度ビルドしておきます。するといくつかのファイルが自動生成されます。
IntelliSenseのエラーが気になりますが、ソリューションを開き直すと改善します。

ここで自分のコードを記述するときに変更する必要があるのが、以下の4ファイルです。

- Class.h
- Class.cpp
- Class.idl
- pch.h

### Class.h, Class.cpp

これは普通のC++のクラスを書くときにもおなじみのペアですね。
基本的にはデフォルトで書かれているコードを真似してメソッドやプロパティを追加していけばよいです。
サンプルの ``MyProperty`` を改造してプロパティを実装し、さらにメソッドを新しく1つ追加しておきます。

```c++:Class.h
#pragma once

#include "Class.g.h"

namespace winrt::RuntimeComponent1::implementation
{
    struct Class : ClassT<Class>
    {
        Class(int32_t initial); // 変更
        int32_t MyProperty();
        void MyProperty(int32_t value);
        int32_t MyMethod();     // 追加
    private:
        int32_t _value;         // 追加
    };
}

// この中は触らなくてOK
namespace winrt::RuntimeComponent1::factory_implementation
{
    struct Class : ClassT<Class, implementation::Class>
    {
    };
}
```

```c++:Class.cpp
#include "pch.h"
#include "Class.h"
#include "Class.g.cpp"

namespace winrt::RuntimeComponent1::implementation
{
    // コンストラクタ
    Class::Class(int32_t initial)
    {
        _value = initial;
    }
    // プロパティ (get)
    int32_t Class::MyProperty()
    {
        return _value;
    }
    // プロパティ (set)
    void Class::MyProperty(int32_t value)
    {
        _value = value;
    }
    // メソッド
    int32_t Class::MyMethod()
    {
        return _value * 2;
    }
}
```

### Class.idl

Microsoft インターフェイス定義言語 (MIDL) で書かれているファイルです。
C#のアプリからブリッジを参照したときにIntelliSenseで表示されるクラス定義を、このファイルによって与えるということのようです。
（Class.cpp, Class.h だけだと普通のネイティブC++のクラスなので、C#から参照するための情報は入っていないみたいです）
[Microsoft インターフェイス定義言語3.0 の概要 - Windows UWP applications | Microsoft Docs](https://docs.microsoft.com/ja-jp/uwp/midl-3/intro)

書き方はC++に似ています。

```c++:Class.idl
namespace RuntimeComponent1
{
    [default_interface]
    runtimeclass Class
    {
        Class(Int32 initial); // 変更
        Int32 MyProperty;
        Int32 MyMethod();     // 追加
    }
}
```

- ``Class(Int32 initial);``
    - コンストラクタ（引数名を value にするとエラーになったので名前を変えました）
- ``Int32 MyProperty;``
    - 引数リストをつけないとプロパティ扱い
- ``Int32 MyMethod();``
    - 引数リストをつけるとメソッド扱い

引数と戻り値のデータ型は、C++で使われるものとは違っていますが、名前から直感的に対応が類推できます。基本の数値データ型には以下があります。（前記公式ドキュメントより）

- Int16
- Int32
- Int64
- UInt8
- UInt16
- UInt32
- UInt64
- Single
- Double

### pch.h

WinRTなどのヘッダファイルを追加する必要があるときに、このファイルに ``#include`` 文を追加します。
今回はデフォルトのまま置いておきます。

ここまでの変更を加えたあと、ビルドができることを確認します。

## C#側

C#プロジェクトのデフォルトのプラットフォームは Any CPU になっていますが、x64 または Win32 に変えておいてください。RuntimeComponent1と合わせておきます（RuntimeComponent1がx86の場合は、WpfApp1側はWin32）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/eb3096b0-2134-ddc1-46dc-f0e46ca5d4f5.png)
WpfApp1から、先ほど作成したRuntimeComponent1を参照します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/84d6eb93-ec88-ef56-a20a-639984df941a.png)
これによって ``RuntimeComponent1`` という名前空間が見えるようになり、その下にある ``Class`` というクラスにアクセスできるはずです。

フォームに適当にボタンを配置し、ボタンを押したらクラスを使って何か処理させてみます。

```csharp:MainWindows.xaml.cs
/* Button_Click() 以外のコードは省略 */
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var cls = new RuntimeComponent1.Class(100);
            MessageBox.Show(cls.MyProperty.ToString());
            MessageBox.Show(cls.MyMethod().ToString());
            cls.MyProperty = 200;
            MessageBox.Show(cls.MyProperty.ToString());
            MessageBox.Show(cls.MyMethod().ToString());
        }
```

確かに ```RuntimeComponent1.Class``` が見えるようになりました。

しかし、この状態ではビルドは通りますが、実行時にエラーが発生します。

>System.TypeLoadException: '要求された Windows ランタイム型 'RuntimeComponent1.Class' は登録されていません。'

MSのサンプルを見ればわかりますが、実はC#側にも「```RuntimeComponent1.Class``` の実装はこのDLLに入っていますよ」という情報を追加してあげないといけません。
プロジェクトに「アプリケーション マニフェスト ファイル」を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/270ce5f9-19b4-71fe-e255-d8523de15090.png)

今回作成したブリッジと、そこに含まれているクラスの情報を追加します。

```xml:app.manifest
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  （略）
	<file name="RuntimeComponent1.dll">
		<activatableClass
			name="RuntimeComponent1.Class"
			threadingModel="both"
			xmlns="urn:schemas-microsoft-com:winrt.v1" />
	</file>
</assembly>
```

- 複数のブリッジライブラリがある場合は、その数だけ ``<file>...</file>`` を追加
- 1つのライブラリの中に複数のクラスがある場合は、``<activatableClass>`` を追加

さらに、C++側で作ったDLLは「VCRT Forwarders」を参照して動くので、C#のexeと同じ場所にこれらのDLLがないといけません。
NuGetパッケージマネージャーから「Microsoft.VCRTForwarders.140」を追加しておくと、C#側のビルド時にexeと同じ場所に必要なDLLがコピーされるようになります。

## 実行

ウィンドウに配置したボタンを押して 100, 200, 200, 400 の順にメッセージが表示されたら成功です。

# 応用例

ここまでの例では、C++/WinRTのブリッジと言いながら、WinRTの機能を全く使っていませんでした。
ということで、実際にWinRTを呼び出す例も見てみましょう。

日本語の形態素解析（文章を単語単位に分割する）APIがあるので、それを使ってみます。
[JapanesePhoneticAnalyzer Class (Windows.Globalization) - Windows UWP applications | Microsoft Docs](https://docs.microsoft.com/ja-jp/uwp/api/windows.globalization.japanesephoneticanalyzer?view=winrt-19041)

与えられた文章に対して、単語リストを返すようなメソッドを作ります。

## C++/WinRT側

### Class.h

```c++:Class.h
#pragma once

#include "Class.g.h"

using namespace winrt::Windows::Foundation::Collections;

namespace winrt::RuntimeComponent1::implementation
{
    struct Class : ClassT<Class>
    {
        Class() = default; // デフォルトコンストラクタ
        static IVectorView<hstring> Class::Analyze(hstring text); // static指定も可能
    };
}

namespace winrt::RuntimeComponent1::factory_implementation
{
    struct Class : ClassT<Class, implementation::Class>
    {
    };
}
```

Class.h で ``Class() = default;`` と書いておくと、処理のないデフォルトコンストラクタを使うことができます。Class.cpp にもコンストラクタの記述は必要ありません。
リストを返すには ``using namespace winrt::Windows::Foundation::Collections;`` を書いた上で ``IVectorView<T>`` インターフェイスを使うことができます。これはC# (.NET) 側から参照すると ``IReadOnlyList<T>`` に見えます。[^1]
また、Unicode文字列を扱うときは ``hstring`` 型を使います。C# (.NET) 側から参照すると ``string`` に見えます。

[^1]: [Windows.Foundation.Collections Namespace - Windows UWP applications | Microsoft Docs](https://docs.microsoft.com/en-us/uwp/api/windows.foundation.collections?view=winrt-19041)

### Class.cpp

```c++:Class.cpp
#include "pch.h"
#include "Class.h"
#include "Class.g.cpp"

using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Globalization;

namespace winrt::RuntimeComponent1::implementation
{
    IVectorView<hstring> Class::Analyze(hstring text)
    {
        auto words = JapanesePhoneticAnalyzer::GetWords(text);
        auto result = winrt::single_threaded_vector<hstring>();
        for (auto word = words.First(); word.HasCurrent(); word.MoveNext()){
            result.Append(word.Current().DisplayText());
        }
        return result.GetView();
    }
}
```

使用したい機能に合わせて適切に ``using namespace`` を記述してください。
また ``winrt::single_threaded_vector<hstring>()`` を使って ``IVector`` オブジェクトの実体を作っています。これを忘れると、NULLポインタアクセスになってアクセス違反となります。

### Class.idl

```c++:Class.idl
namespace RuntimeComponent1
{
    [default_interface]
    runtimeclass Class
    {
        Class();
        static Windows.Foundation.Collections.IVectorView<String> Analyze(String text);
    }
}
```

idlでクラスの名前空間を書くときはドット区切り (``Windows.Foundation.Collections.IVectorView``) になります。
また、文字列型はidlでは ``String`` となります。

### pch.h

```c++:pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Globalization.h>
```

使いたい機能に合わせてヘッダファイルの記述を追加します。

## C#側

```csharp:MainWindows.xaml.cs
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var result = RuntimeComponent1.Class.Analyze("本日は晴天なり");
            MessageBox.Show(string.Join(",", result));
        }
```

C#側のプロジェクトで、参照にC++側のプロジェクトを追加してビルド・実行し、「本日,は,晴天,なり」と単語単位で区切られたメッセージが出たら成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/fd4c7320-964f-abf3-b497-0b563d6e0a19.png)

## 身も蓋もない話

正直この程度だったらブリッジを作るまでもなくて、以下の方法でもっと手軽にできたりします。
[\[C#\] デスクトップアプリ (WPF) から手軽にWinRT APIを活用しよう - Qiita](https://qiita.com/everylittle/items/62ce313fe09883c6da5f)

ただポインタやATLなど色々触り出すと、下手にC#で書くよりC++でブリッジを作ったほうがたぶん楽です。

# トラブルシューティング

## ブリッジクラスがC#側から見えない

C#側から参照にC++プロジェクトを追加していても見えない場合は、idlファイルの記述をご確認ください。
idlファイルを編集したら「リビルド」したほうが良いです。

## エラー C2660 'winrt::RuntimeComponent1::implementation::Class::MyMethod': 関数に 1 個の引数を指定できません。	

Class.idl に書いた名前と引数で呼び出せるメソッドが、Class.cpp, Class.h 側に定義されていないと思われます。（オーバーロードは可能ですが、idlの記述に不足がないようにご注意ください）

## System.TypeLoadException: '要求された Windows ランタイム型 'RuntimeComponent1.Class' は登録されていません。'

app.manifest の ``<file>`` や ``<activatableClass>`` の記述が不足しているようです。

## System.BadImageFormatException: ' は有効な Win32 アプリケーションではありません。 (HRESULT からの例外:0x800700C1)'

C++側とC#側のプラットフォーム設定が合っていないみたいです。x64同士、またはx86/Win32の組み合わせであることを確認してください。前述のように Any CPU はダメです。

## System.IO.FileNotFoundException: '指定されたモジュールが見つかりません。 (HRESULT からの例外:0x8007007E)'

まずは RuntimeComponent1 のビルドが成功していて、C#側のプロジェクトで指定したexeの出力先と同じ場所に RuntimeComponent1.dll がコピーされているか確認してください。
もし RuntimeComponent1.dll があるのにこのエラーが出るときは、VCRT ForwardersのDLLが見つからないのが原因と思われます。

- NuGetパッケージマネージャーの設定で、既定のパッケージ管理方法を「Packages.config」にする
- C#側のプロジェクトに Microsoft.VCRTForwarders.140 を追加する（既に追加されている場合で、後からPackages.configの設定を変えたときは、VCRTForwardersを一度削除して再度追加する）

ビルドしたあとに ``bin\x64\Debug`` の下などに ``msvcp140`` や ``vcruntime140`` などの名前で始まるDLLが大量にコピーされたら成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/0f75e1cc-8715-f3ba-0d9e-6efcff5ec8d8.png)

## 0x00007FF90CD1DD44 (RuntimeComponent1.dll) で例外がスローされました (WpfApp1.exe 内): 0xC0000005: 場所 0x0000000000000000 の読み取り中にアクセス違反が発生しました

``IVector`` の初期化ができているかご確認ください。``winrt::single_threaded_vector`` をお忘れなく。