#基本的なクラスのラッピング
simple_class.hppのような単純なクラス(構造体)をラップする際にはsimple_wrapper.hppのようにします。
class simple_class
{
public:
simple_class();
~simple_class();
simple_class(const simple_class *obj);
int hoge;
bool fuga;
char mojis[16];
};
#ifndef SIMPLE_WRAPPER_HPP
#define SIMPLE_WRAPPER_HPP
#include "simple_class.hpp"
public ref class SimpleWrapper
{
internal:
bool isResponsible;
simple_class *p;
public:
SimpleWrapper() : isResponsible { true }, p { new simple_class() } {}
SimpleWrapper(SimpleWrapper^ obj) : isResponsible { true }, p { new simple_class(obj->p) } {}
SimpleWrapper(simple_class *p) isResponsible { false }, p { p } {}
// マネージド及びアンマネージドリソースの解放
~SimpleWrapper() { this->!SimpleWrapper(); }
// アンマネージドリソースの解放
!SimpleWrapper()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
property int Hoge
{
int get() { return this->p->hoge; }
void set(int value) { this->p->hoge = value; }
}
property bool Fuga
{
bool get() { return this->p->fuga; }
void set(bool value) { this->p->fuga = value; }
}
property char Mojis[int]
{
char get(int index)
{
if(index < 0 || index >= 16) throw gcnew System::IndexOutOfRangeException();
return this->p->mojis[index];
}
void set(int index, char value)
{
if(index < 0 || index >= 16) throw gcnew System::IndexOutOfRangeException();
this->p->mojis[index] = value;
}
}
};
#endif //SIMPLE_WRAPPER_HPP
.NETからはC++のclassやstructを直接使用することができません。
そのため、C++/CLIでは.NETから利用できるものとしてref class, value class, enum classを用意しています。それぞれC#のclass, struct, enumに対応しています。
様々な制限があるためにC++/CLIによるC++コードラッピングでvalue classを使用することはあまりありません。
ref classによってC++のclassやstructをラップしますが、マネージド型はアンマネージド型を直接メンバに持つことはできません。アンマネージド型のポインタならばメンバに持つことができます。
そのため、SimpleWrapperではinternalメンバにsimple_classポインタを持っています。
プロパティ
ラップ元のclassのpublicフィールドはプロパティによってラップすべきです。
インデクサ
C++/CLIにおけるインデクサは主にラップ元のpublicな配列をラップするために使用されます。
#他のC++クラス型やそのポインタ型をメンバに持つクラスのラッピング
class baz
{
public:
baz();
~baz();
int x;
};
struct bar
{
int id;
unsigned char flags[2];
};
class foo
{
public:
foo();
~foo();
bar x;
baz *y;
};
#ifndef FOO_BAR_BAZ_HPP
#define FOO_BAR_BAZ_HPP
#include "foo_and_bar_baz.hpp"
public ref class Baz
{
internal:
bool isResponsible;
baz *p;
public:
Baz() : isResponsible { true }, p { new baz() } {}
Baz(baz *p) : isResponsible { false }, p { p } {}
~Baz() { this->!Baz(); }
!Baz()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
property int X
{
int get() { return this->p->x; }
void set(int value) { this->p->x = value; }
}
};
public ref class Bar
{
internal:
bool isResponsible;
bar *p;
public:
Bar() : isResponsible { true }, p { new bar() } {}
Bar(bar *p) : isResponsible { false}, p { p } {}
~Bar() { this->!Bar(); }
!Bar()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
property int id
{
int get() { return this->p->id; }
void set(int value) { this->p->id = value; }
}
property unsigned char Flags[int]
{
unsigned char get(int index)
{
if(index < 0 || index >= 2) throw gcnew System::IndexOutOfRangeException();
return this->p->flags[index];
}
unsigned char set(int index, unsigned char value)
{
if(index < 0 || index >= 2) throw gcnew System::IndexOutOfRangeException();
this->p->flags[index] = value;
}
}
};
public ref class Foo
{
internal:
bool isResponsible;
foo *p;
public:
Foo() : isResponsible { true }, p { new foo() } {}
Foo(foo *p) : isResponsible { false }, p { p } {}
~Foo() { this->!Foo(); }
!Foo()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
property Bar^ X
{
Bar^ get(){ return gcnew Bar(&this->p->x); }
void set(Bar^ value){ this->p->x = *value->p; }
}
property Baz^ Y
{
Baz^ get(){ return gcnew Baz(this->p->y); }
void set(Baz^ value){ this->p->y = value->p; }
}
};
#endif //FOO_BAR_BAZ_HPP
C++/CLIではマネージド型を扱う際にハンドル(^)を型名に付けます。ref classを引数や戻り値、メンバにする時には必ず型名^としなければなりません。
##列挙型を含むクラス
value class(C#の構造体)やenum class(C#の列挙型)にはハンドルをつける必要はありません。
struct hoge
{
int month;
};
#define Jan 1
#define Feb 2
#define Mar 3
#define Apr 4
#define May 5
#define Jun 6
#define Jul 7
#define Aug 8
#define Sep 9
#define Oct 10
#define Nov 11
#define Dec 12
#ifndef ENUM_HPP
#define ENUM_HPP
#include "hoge.hpp"
public enum class Month { Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec}
public ref class Hoge
{
internal:
bool isResponsible;
hoge *p;
public:
Hoge() : isResponsible { true }, p { new Hoge() } {}
Hoge(hoge *p) : isResponsible { false }, p { p } {}
~Hoge() { this->!Hoge(); }
!Hoge()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
property Month Month
{
Month get() { return static_cast<Month>(this->p->month); }
void set(Month value) { this->p->month = static_cast<int>(value); }
}
};
#endif //ENUM_HPP
#C++のtemplate型のラッピング
C++のテンプレートに対応するものとしては.NETのジェネリクスがありますが、ラッピングにおいてジェネリクスを使うことはできません。
テンプレートの特殊化された事例個々に対してラップしましょう。
##STLのコレクション型のラッピング
以下のプログラムにおいてfoo, barはアンマネージド型、Foo, Barはマネージド型とします。
#include <unordered_map>
#include <vector>
typedef unsigned char byte;
struct bar{ /*省略*/ };
struct foo
{
std::unordered_map<byte, bar*> hoge;
};
#ifndef STL_WRAPPER_HPP
#define STL_WRAPPER_HPP
#include "STL_Sample.hpp"
using namespace std;
public ref class Bar{ /* 省略 */ };
// unordered_map<byte, bar*> のラッパー
public ref class UMap_Byte_Bar : System::Collections::Generic::IDictionary<byte, Bar^>
{
internal:
bool isResponsible;
unordered_map<byte, bar*> *p;
public:
ref class Enumerator : System::Collections::Generic::IEnumerator<System::Collections::Generic::KeyValuePair<byte, Bar^>>
{
internal:
unordered_map<byte, bar*> *beginp, *itrp, *endp;
public:
Enumerator(UMap_Byte_Bar parent) : beginp{ new unordered_map<byte, bar*>::iterator(parent->p->begin()) }, itrp{ nullptr }, endp{ new unordered_map<byte, bar*>::iterator(parent->p->end()) } {}
~Enumerator(){ this->!Enumerator(); }
!Enumerator()
{
if(beginp)
{
delete beginp;
beginp = nullptr;
}
if(itrp)
{
delete itrp;
itrp = nullptr;
}
if(endp)
{
delete endp;
endp = nullptr;
}
}
virtual bool Reset() = System::Collections::IEnumerator::Reset
{
if(itrp)
{
delete itrp;
itrp = nullptr;
}
}
virtual bool MoveNext() = System::Collections::IEnumerator::MoveNext
{
if(!itrp) itrp = new unordered_map<byte, bar*>::iterator(*beginp);
else ++(*itrp);
return *itrp != *endp;
}
virtual KeyValuePair<byte, Bar^> Current
{
KeyValuePair<byte, Bar^> get()
{
return KeyValuePair<byte, Bar^>(itrp->first , gcnew Bar(itrp->second));
}
}
virtual System::Object^ Current_IEnumerator
{
System::Object^ get() = System::Collections::IEnumerator::Current::get { return Current; }
}
}
UMap_Byte_Bar() : isResponsible { true }, p { new unordered_map<byte, bar*>() } {}
UMap_Byte_Bar(unordered_map<byte, bar*> *p) : isResponsible { false }, p { p } {}
~UMap_Byte_Bar() { this->!UMap_Byte_Bar(); }
!UMap_Byte_Bar()
{
if(isResponsible && p)
{
delete p;
p = nullptr;
}
}
virtual property int Count
{
int get() = System::Collections::Generic::ICollection<byte, Bar^>::Count::get { return p->size; }
}
virtual property bool IsReadOnly
{
bool get() = System::Collections::Generic::ICollection<byte, Bar^>::IsReadOnly::get { return false; }
}
virtual property Bar^ Item[byte]
{
Bar^ get(byte index) { return gcnew Bar(p->at(index)); }
void set(byte index, Bar^ value) { p->at(index) = value->p; }
}
virtual property System::Collections::Generic::ICollection<byte>^ Keys
{
System::Collections::Generic::ICollection<byte>^ get()
{
auto answer = gcnew List<byte>();
for(auto pair : *p)
answer->Add(pair.first);
return answer;
}
}
virtual property System::Collections::Generic::ICollection<Bar^>^ Values
{
System::Collections::Generic::ICollection<Bar^>^ get()
{
auto answer = gcnew List<Bar^>();
for(auto pair : *p)
answer->Add(gcnew Bar(pair.second));
return answer;
}
}
virtual void Add(System::Collections::Generic::KeyValuePair<byte, Bar^> item) = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::Add
{
auto key = item.Key;
auto value = item.Value->p;
p->insert(pair<byte. bar*>(key, value));
}
virtual void Add(byte key, Bar^ value)
{
auto valuep = value->p:
p->insert(key, valuep);
}
virtual void Clear() = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::Clear { p->clear(); }
virtual bool Contains(System::Collections::Generic::KeyValuePair<byte, Bar^> item) = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::Contains
{
auto key = item.Key;
auto value = item.Value->p;
auto range = p->equal_range(key);
for(; range.first != range.second; ++range)
if(range.first->second == value)
return true;
return false;
}
virtual bool ContainsKey(byte key) { return p->find(key) != p->end(); }
virtual void CopyTo(cli::array<Bar^>^ array, int arrayIndex) = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::CopyTo
{
int index = 0;
for(auto pair : *p)
array[arrayIndex + index++] = gcnew Bar(pair.second);
}
virtual System::Collections::Generic::IEnumerator<KeyValuePair<byte, Bar^>>^ GetEnumerator() = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::GetEnumerator
{
return gcnew Enumerator(this);
}
virtual System::Collections::IEnumerator GetEnumerator_IEnumerator() = System::Collections::IEnumerable::GetEnumerator{ return GetEnumerator(); }
virtual bool Remove(System::Collections::Generic::KeyValuePair<byte, Bar^> item) = System::Collections::Generic::ICollection<KeyValuePair<byte, Bar^>>::Remove
{
auto key = item.Key;
auto value = item.Value->p;
auto range = p->equal_range(key);
for(; range.first != range.second; ++range)
if(range.first->second == value){
p->erase(range.first);
return true;
}
return false;
}
virtual bool Remove(byte key) { return p->erase(key); }
virtual bool TryGetValue(byte key, [System::Runtime::InteropServices::Out]Bar^% value)
{
auto itr = p->find(key);
if(itr == p->end())
{
value = nullptr;
return false;
}
value = gcnew Bar(itr->second);
return true;
}
};
public ref class Foo
{
internal:
// 省略
UMap_Byte_Bar^ _Hoge;
public:
// 前略
property UMap_Byte_Bar^ Hoge
{
UMap_Byte_Bar^ get()
{
if(_Hoge) return _Hoge;
return _Hoge = gcnew UMap_Byte_Bar(&this->p->hoge);
}
}
};
#endif //STL_WRAPPER_HPP
.NETから使用しやすいようにunordered_mapはIDictionaryを是非実装しましょう。
その際に注意すべきこととしてはインターフェイスの明示的実装が必要なメソッドやプロパティがいくらかあることです。
明示しなくていい場合と明示しなくてはならな場合の違いはよくわかりません。Visual Studioが注意してくれますのでそれに従いましょう。
##STL/CLRについて
使わなくていいです。
C++標準のSTLとMicrosoftのSTL/CLRは名前と挙動が同じだけです。
相互に変換することもできません。
素直にラッパクラスを自動生成しましょう。
#他に注意すべきこと
##配列を引数に持つメソッド
.NETの配列はマネージド型であり、そのままではC++の配列として扱うことはできません。
C++/CLIのメソッド内でアンマネージド配列を用意してラップ元のメソッドに引き渡すこともできますが、それはメモリの無駄遣いです。
そのため、C++/CLIにはpin_ptr型が用意されています。
配列の先頭のポインタで初期化されたpinn_ptr変数はnullptrを代入されるまでガベージコレクタを停止させます。
そのため、使用を済ませ次第速やかにnullptrを代入して始末してください。
int hoge(int[] array, int count);
#ifndef ARRAY_WRAPPER_HPP
#define ARRAY_WRAPPER_HPP
#include "array.hpp"
ref class StaticFunctions
{
public:
static int Hoge(cli::array<int, 1>^ array, int count)
{
pin_ptr<int> p{ &array[0] };
auto answer = ::hoge(p, count);
p = nullptr;
return answer;
}
}
#endif //ARRAY_WRAPPER_HPP
##unionのラッピング
propertyできれいにラップしましょう。
#あとがき
大規模なWindows用C++ソリューションをラップするならClangのlibToolingを使用することをお勧めします。
特にSTLのラップは単なる繰り返しになりますからソースコードに基づいた自動化が素晴らしい効力を発揮することでしょう。
ClangのlibToolingでASTをダンプするツールを作ってみた(その2) がおすすめです。