LoginSignup
14
15

More than 5 years have passed since last update.

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

Posted at

はじめに

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パターンも使える 

14
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15