CLIラッパの作り方

ネイティブコードのHogeCクラスを含むライブラリHogeC.libを、C#で利用するためのCLIラッパライブラリHogeSharp.dllの作り方をメモします。

プロジェクトの作成

[ファイル]>[新しいプロジェクト]>[テンプレート]>[Visual C++]>[CLR]>[クラスライブラリ]で新規プロジェクトを作成します。ここでは HogeSharp という名前にします。

プロジェクトの設定

  • ネイティブライブラリのヘッダ HogeC.h を任意のフォルダに置きます。ここではソースフォルダの下のIncフォルダとします。
  • ネイティブライブラリ HogeC.lib を任意のフォルダに置きます。ここではソースフォルダの下のLibフォルダとします。
  • [プロジェクト]>[プロパティ]>[構成プロパティ]>[VC++ディレクトリ]>[インクルードディレクトリ]に、Incを追加します。(;で区切ります)
  • [プロジェクト]>[プロパティ]>[構成プロパティ]>[VC++ディレクトリ]>[ライブラリディレクトリ]に、Libを追加します。(;で区切ります)
  • [プロジェクト]>[プロパティ]>[構成プロパティ]>[リンカ]>[入力]>[追加の依存ファイル]に、HogeC.libを追加します。(;で区切ります)
  • その他、HogeC.libがさらに別のライブラリに依存している場合、それらも追加します。(たとえば、WinSock APIを使用していて ws2_32.libに依存しているなど)

ソースファイル一覧

resource.h

特に修正不要。

AssemblyInfo.cpp

名称、著作権表示、バージョン情報などの属性を適当に記入するだけです。

Stdafx.h, Stdafx.cpp

使用するインクルードファイルやライブラリをStdafx.hに列挙します。(べつに書かなくても良い?)

#include "HogeC.h"
#pragma comment(lib,"HogeC.lib")

Stdafx.cppはただのダミーなので修正不要です。

HogeSharp.h, HogeSharp.cpp

おもにコードを書くべきはここです。

コードの記述

ラッパクラスの宣言

HogeSharp.h に Class1 というクラスが勝手に作られるので、これを Hoge に改名します。このクラスが、public ref classであることに注意してください。C#から使用するクラスは、public ref classでないといけません。ちなみにC#で構造体として使うのであれば、pubilc value classとします。また、クラスHogeは、名前空間HogeSharpに属します。
Hogeクラスには、publicなメンバとしてコンストラクタ、デストラクタ、ファイナライザ、および必要なメソッドを宣言します。ファイナライザというのはCLI特有のものです。C#のデストラクタ~Hoge()に対応するのが、CLIではファイナライザ!Hoge()であり、CLIのデストラクタ~Hoge()は、C#Hoge.Dispose()に対応します。

また、privateなメンバとして、ネイティブクラスHogeCへのポインタを宣言します。C#から呼び出すラッパクラスは、ネイティブクラス(アンマネージドクラス)の実体をメンバに持てないことに注意してください。

    // CLIのラッパクラス
    public ref class Hoge
    {
    public:
        Hoge();    // コンストラクタ
        ~Hoge();   // デストラクタ
        !Hoge();   // ファイナライザ
        // その他、必要なメソッド
        int Piyo();
        void Huga(int Foo);
    private:
        HobeC* hogeC; // ネイティブクラスへのポインタ
    };
ラッパクラスの実装

HogeクラスをHogeSharp.cppに実装します。

  • コンストラクタでHobeCオブジェクトをnewします。
  • ファイナライザでHobeCオブジェクトをdeleteします。
  • デストラクタではファイナライザを呼ぶことにしておきます。

※ デストラクタではマネージドリソースを開放してからファイナライザを呼び出し、ファイナライザでアンマネージドリソースを解放するべきだそうです。

  • 各メソッドは、ネイディブクラスの対応するメソッドを呼び出します。
using namespace HogeSharp;

// コンストラクタ
Hoge::Hoge() {
    hogeC = new HogeC(); // ネイティブオブジェクト生成
}

// デストラクタ
Hoge::~Hoge() {
    this->!Hoge(); // ファイナライザを呼ぶ
}

// ファイナライザ
Hoge::!Hoge() {
    delete hogeC; // ネイティブオブジェクト解放
}
// ラッパ
int Hoge::Piyo()
{
    return hogeC->Piyo();
}
void Hoge::Huga(int Foo)
{
    hogeC->Huga(Foo);
}
.NETライブラリの使用

ウィザードが吐くコードの中に、すでに using namespace System; があるので、System名前空間のクラスは利用できます。

using namespace System;

String^ hoge;

それ以外の名前空間のクラス、例えばSystem.Drawing.Bitmapを使用したい場合は、下記のように書きます。

#using <system.drawing.dll>
using namespace System::Drawing;

Bitmap^ piyo;
型と参照

ハンドル
C#の参照型に相当する型の変数宣言には、^ をつけます。これをハンドルと呼びます。

Bitmap^ piyo;

この場合のオブジェクト生成(C#でのnewに相当)は、newではなくgcnewを用います。

String^ foo = gcnew String("FOO");



追跡参照
C#のrefに相当する引数宣言には、% をつけます。これを追跡参照とよびます。

// C# からは hoge.piyo(ref foo);のように呼ぶ
void Hoge::piyo(int% foo)

また、C#のoutに相当する引数宣言は、下記のようにします。

// C# からは hoge.piyo(out foo);のように呼ぶ
void Hoge::piyo([Runtime::InteropServices::Out]int% foo) 
文字列の変換

C#のSystem.Stringと、C++のstd::string、Cのchar*の変換は下記のようにします。

#include <msclr/marshal_cppstd.h>
using namespace msclr::interop;

// C#のString => C++のstring, Cのchar*
System::String^ s_cs = L"文字列";
std::string s_cpp = marshal_as<std::string>(s_cs);
const char* s_c = s_cpp.c_str();

// C++のstring => C#のString
std::string s_cpp = "文字列";
System::String^ s_cs = marshal_as<System::String^>(s_cpp);

// Cのchar* => C#のString
const char* s_c = "文字列";
System::String^ s_cs = marshal_as<System::String^>(s_c);

ビルド

ビルドするとHogeSharp.dllができます。

C#アプリでの使用

  • ソースのフォルダにHogeSharp.dllを追加します。
  • [プロジェクト] > [参照の追加] > [参照] で上記DLLを追加します。
  • using HogeSharp; とすれば、クラスHogeが使えます。