C++
C#
C++-CLI
.NETFramework

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


はじめに

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つのラッパーを作成します。


  1. マネージドコードで定義したアンマネージドクラス

    .NETのクラスをメンバーに持ち、メソッドを呼び出します。

  2. アンマネージドコードで定義したアンマネージドクラス

    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の両方でコンパイルできるように注意します。


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#のメソッドを呼び出すクラスを作成します。


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++でコンパイルします。

#include "wrapper.h"


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

実行結果

Hello, C#


参考サイト


余談

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





  1. 正確には.NET Frameworkのアセンブリに格納されているメソッド 



  2. IDLを書かなくても.NETのアセンブリさえあればTLBファイルが生成できる 



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