C#のメソッドをC++から呼ぶ方法

# はじめに

C++(≠C++/CLI)からC#のメソッド[^otherlang]を呼び出すには次の方法があります。

* COMを使用する  
すべての.NETアセンブリはCOMコンポーネント[^net-to-c-bridge]なので簡単にできそうですが、ジェネリクスの扱いに難がありそうな予感がします。今回は調べていません。
* C++/CLIでラッパーを作成する  
今回調べた方法です。

.NET Core用のC++/CLIが無いので対象は.NET Frameworkです。

# マネージド/アンマネージド

C++/CLIではマネージド/アンマネージドの区別が重要になります。

## マネージド/アンマネージドコード

* マネージドコード(デフォルト)
    * ILにコンパイルされます。
    * .NETから呼び出せます。
    * エクスポート(dllexport)できません。従って普通のC++から呼び出せません。
* アンマネージドコード
    * 機械語にコンパイルされます。
    * エクスポート(dllexport)でき、普通のC++から呼び出せます。

C++/CLIで作成するDLLにはマネージドコードとアンマネージドコードを共存させることができます。
それぞれ`#pragma managed`と`#pragma unmanaged`で切り替えられます。

## マネージド/アンマネージドクラス

* マネージドクラス
    * .NETの参照型に対応します。
    * マネージドコードで定義する必要があります。
    * マネージドクラスを集約できます(メンバー変数に持てます)。
    * GCの対象になります。
* アンマネージドクラス
    * .NETの値型に対応します。
    * マネージドクラスを集約できません。
    * GCの対象になりません。

### ハンドル  

ハンドルはマネージドクラス専用のポインターのようなもので、`T*` ではなく`T^`という型名を持ちます。

```cpp
// アンマネージドクラス
T* v = new T()
// マネージドクラス
T^ v = gcnew T()
```

アンマネージドクラスはハンドルを直接メンバー変数に持つことができません。
ただし`gcroot`というクラステンプレートでラップすることでメンバー変数にできます。

# 戦略

C++/CLIの最もよくある使われ方は、C++のライブラリをC#(.NET)で使えるようにするために使う事でしょう。この時は、C++クラスのラッパーをマネージドクラスで作成することになります。

逆はもうひと手間掛かります。

* C++から呼び出すためには、アンマネージドコードで定義したアンマネージドクラスでなければなりません。
* .NETのメソッドを呼び出すためには、マネージドコードでなければなりません。

これらの制限を超えるため、2つのラッパーを作成します。

1. マネージドコードで定義したアンマネージドクラス  
.NETのクラスをメンバーに持ち、メソッドを呼び出します。
2. アンマネージドコードで定義したアンマネージドクラス  
1.のラッパーでありC++にエクスポートするクラスです。

2.はC++側に見せるため、1.の実装に依存できません。2.を基本クラスとし、1の実装は隠蔽します[^pimpl]。

[^pimpl]: アンマネージドクラスならポインターは見えてもよいので、pimplパターンも使える

# 実装

簡単のため、以下のシンプルなC#のクラスを作成します。これをC++から呼び出します。

```csharp
using System;

namespace ManagedLibrary
{
    public class ManagedClass
    {
        public void SayHello()
        {
            Console.WriteLine("Hello, C#");
        }
    }
}
```

## ラッパーを作成する

まずC++にエクスポートするクラスを作成します。ヘッダーファイルがC++とC++/CLIの両方でコンパイルできるように注意します。

```cpp:wrapper.h
#pragma once
#pragma unmanaged
#include <memory>

#ifdef WRAPPER
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif

namespace Wrapper
{
    class Wrapper {
    public:
        virtual void SayHello() = 0;
        virtual ~Wrapper() = default;
    private:
        Wrapper(const Wrapper&) = delete;
        Wrapper(Wrapper&&) = delete;
        Wrapper& operator=(const Wrapper&) = delete;
        Wrapper& operator=(Wrapper&&) = delete;
    };

    EXPORT extern std::unique_ptr<Wrapper> CreateWrapper();
}
```

ファクトリーメソッドも宣言しました。生の`new`は邪悪ですから`unique_ptr`を使用します。

次にC#のメソッドを呼び出すクラスを作成します。

```cpp:wrapper.cpp
#include "wrapper.h"
#pragma managed
#include <gcroot.h>

namespace Wrapper {
    public class WrapperImpl : public Wrapper
    {
    public:
        WrapperImpl()
        {
            managedObject = gcnew ManagedClass();
        }

        void SayHello() override
        {
            managedObject->SayHello();
        }

    private:
        gcroot<ManagedClass^> managedObject;
    };

    std::unique_ptr<Wrapper> CreateWrapper()
    {
        return std::make_unique<WrapperImpl>();
    }
}
```

## ラッパーを利用する

最後に次の簡単なアプリケーションでラッパーを利用してみましょう。C++/CLIではなくC++でコンパイルします。

```cpp
#include "wrapper.h"

int main()
{
    Wrapper::CreateWrapper()->SayHello();
}
```

実行結果

```
Hello, C#
```

# 参考サイト

* [.NET to C++ Bridge](http://blogs.microsoft.co.il/sasha/2008/02/16/net-to-c-bridge/)

# 余談

`WrapperImpl::managedObject`はクラス内メンバーイニシャライザで初期化したいところですが、デフォルトコンストラクタを`{}`ではなく`=default`で実装したところ、アンマネージドコードで`gcnew`は使用できない(C3821)と言われてしまいました。ハマりそうなので普通にコンストラクタ内で初期化しています。

[^otherlang]: 正確には.NET Frameworkのアセンブリに格納されているメソッド
[^net-to-c-bridge]: IDLを書かなくても.NETのアセンブリさえあればTLBファイルが生成できる