はじめに
C++(≠C++/CLI)からC#のメソッド1を呼び出すには次の方法があります。
- COMを使用する
すべての.NETアセンブリはCOMコンポーネント2なので簡単にできそうですが、ジェネリクスの扱いに難がありそうな予感がします。今回は調べていません。 - 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^
という型名を持ちます。
// アンマネージドクラス
T* v = new T()
// マネージドクラス
T^ v = gcnew T()
アンマネージドクラスはハンドルを直接メンバー変数に持つことができません。
ただしgcroot
というクラステンプレートでラップすることでメンバー変数にできます。
戦略
C++/CLIの最もよくある使われ方は、C++のライブラリをC#(.NET)で使えるようにするために使う事でしょう。この時は、C++クラスのラッパーをマネージドクラスで作成することになります。
逆はもうひと手間掛かります。
- C++から呼び出すためには、アンマネージドコードで定義したアンマネージドクラスでなければなりません。
- .NETのメソッドを呼び出すためには、マネージドコードでなければなりません。
これらの制限を超えるため、2つのラッパーを作成します。
- マネージドコードで定義したアンマネージドクラス
.NETのクラスをメンバーに持ち、メソッドを呼び出します。 - アンマネージドコードで定義したアンマネージドクラス
1.のラッパーでありC++にエクスポートするクラスです。
2.はC++側に見せるため、1.の実装に依存できません。2.を基本クラスとし、1の実装は隠蔽します3。
実装
簡単のため、以下のシンプルなC#のクラスを作成します。これをC++から呼び出します。
using System;
namespace ManagedLibrary
{
public class ManagedClass
{
public void SayHello()
{
Console.WriteLine("Hello, C#");
}
}
}
ラッパーを作成する
まずC++にエクスポートするクラスを作成します。ヘッダーファイルがC++とC++/CLIの両方でコンパイルできるように注意します。
#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#のメソッドを呼び出すクラスを作成します。
#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++でコンパイルします。
#include "wrapper.h"
int main()
{
Wrapper::CreateWrapper()->SayHello();
}
実行結果
Hello, C#
参考サイト
余談
WrapperImpl::managedObject
はクラス内メンバーイニシャライザで初期化したいところですが、デフォルトコンストラクタを{}
ではなく=default
で実装したところ、アンマネージドコードでgcnew
は使用できない(C3821)と言われてしまいました。ハマりそうなので普通にコンストラクタ内で初期化しています。