目的
たまーにC++/CLI (Common Language Infrastructure) を使うことがあるが、たまにしか使わない。
なので、使おうとするたびに忘れて、思い出すのに手間取る記法やらを、他でもない自分のためにまとめておく。
C++/CLIが初めてという方は、以下のサイトで丁寧にわかりやすく解説されているので、そちらを参照されたい。
C++/CLI入門 - WisdomSoft
プロジェクトの設定
スクリーンショットはVS2017のもの。
ネイティブコードと混ぜるときは「共通言語ランタイム サポート(/clr)」を選ぶ。
Hello, World!
#using <mscorlib.dll>
int main(int argc, char **argv)
{
System::Console::WriteLine("Hello, world!");
return 0;
}
マネージドクラス
クラス定義とインスタンス生成
ref class
でクラス定義。インスタンスの変数は^
をつけ、インスタンス生成にはgcnew
を用いる。
public ref class ClassA
{
public:
int field;
void showField()
{
System::Console::WriteLine("field is " + field);
}
};
int main(int argc, char **argv)
{
ClassA^ a = nullptr; // 無効な参照はnullptrキーワードで表す
a = gcnew ClassA();
a->field = 22;
a->showField(); // field is 22
return 0;
}
ネイティブクラスのメンバ変数としてマネージドクラスの参照を持つ
ネイティブクラスのメンバ変数として、上記のマネージドクラスClassA
のインスタンスへの参照を持たせるには、gcroot
を使う。
#include <vcclr.h>
// ClassAの定義は先述と同様のため省略
public class NativeClass
{
public:
void setClassA(ClassA^ a)
{
this->classA = a;
}
ClassA^ getClassA()
{
return this->classA;
}
void showMsg()
{
this->classA->showField();
}
private:
gcroot<ClassA^> classA;
};
int main(int argc, char **argv)
{
NativeClass nc;
ClassA^ a = gcnew ClassA();
a->field = 44;
nc.setClassA(a);
nc.showMsg(); // field is 44
return 0;
}
プロパティ
下記の例でName
はトリビアルプロパティ(Name
を格納するためのprivate
フィールドが自動生成される)。
Age
はカスタムのゲッター・セッターを定義している。
public ref class User
{
private:
int _Age;
public:
property System::String^ Name;
property int Age
{
int get()
{
return this->_Age;
}
void set(int value)
{
if (value >= 0)
{
this->_Age = value;
}
}
}
};
上記のように定義したUser
クラスをC#から利用した例が以下。NewtonsoftJson.JsonConvert
にもかけられる。
static void Main(string[] args)
{
CLILib.User user = new CLILib.User();
user.Name = "John";
user.Age = 29;
System.Console.WriteLine("Name: {0}, Age: {1}", user.Name, user.Age);
// Name: John, Age: 29
user.Age = -3;
System.Console.WriteLine("Name: {0}, Age: {1}", user.Name, user.Age);
// Name: John, Age: 29
// Ageのsetの値チェックに引っかかるため、負の値は代入されない。
string json = Newtonsoft.Json.JsonConvert.SerializeObject(user);
System.Console.WriteLine(json);
// {"Age":29,"Name":"John"}
}
マネージ配列
int main(int argc, char **argv)
{
cli::array<int>^ iAry1 = gcnew cli::array<int>(5); // サイズ5の配列。
cli::array<int>^ iAry2 = gcnew cli::array<int>(3) { 2, 4, 6 }; // 要素を初期化
cli::array<int>^ iAry3 = { 10, 20, 30 }; // gcnew~は省略可能
auto printArray = [](cli::array<int>^ a)
{
// イテレートは正直にfor文を使う。
for (int i = 0, ii = a->Length; i < ii; i++)
{
System::Console::Write(a[i] + " ");
}
System::Console::WriteLine();
};
printArray(iAry1); // 0 0 0 0 0
printArray(iAry2); // 2 4 6
printArray(iAry3); // 10 20 30
return 0;
}
List
int main(int argc, char **argv)
{
// これを書くとList変数宣言時の名前空間記述を省略できる。
using namespace System::Collections::Generic;
// 生成
List<int>^ iList = gcnew List<int>();
// 要素の追加
iList->Add(10);
iList->Add(20);
// 引数に{31, 32, 33}とだけ書くとコンパイルエラー。
// 明示的にcli::arrayをgcnewする必要がある。
iList->AddRange(gcnew cli::array<int>(3) { 31, 32, 33 });
// イテレート。
for (int i = 0, ii = iList->Count; i < ii; i++)
{
System::Console::Write(iList[i] + " ");
}
System::Console::WriteLine();
// 出力:
// 10 20 31 32 33
return 0;
}
このとき、リストに対して直接[]
でアクセスしようとすると、IntelliSenseにより下図のように「式にはpointer-to-object型またはhandle-to-C++/CLI-array型が必要です」と表示されるが、ビルドは正常に通り、問題なく動作する。
その他
備忘したい事項が出てきたら随時追記予定。
参考
C++/CLIの入門的な部分:
C++/CLI入門 - WisdomSoft
C++/CLIのマネージドクラスでのプロパティについて:
C++/CLI TIps:プロパティ