忘れっぽい僕のためのC++/CLIチートシート

# 目的

たまーにC++/CLI (Common Language Infrastructure) を使うことがあるが、たまにしか使わない。
なので、使おうとするたびに忘れて、思い出すのに手間取る記法やらを、他でもない自分のためにまとめておく。
C++/CLIが初めてという方は、以下のサイトで丁寧にわかりやすく解説されているので、そちらを参照されたい。
[C++/CLI入門 - WisdomSoft](http://www.wisdomsoft.jp/371.html)

# プロジェクトの設定

スクリーンショットはVS2017のもの。
ネイティブコードと混ぜるときは「共通言語ランタイム サポート(/clr)」を選ぶ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/f8f8d6a8-e0a6-195e-c638-b97817d5d495.png)

# Hello, World!

```C++
#using <mscorlib.dll>

int main(int argc, char **argv)
{
	System::Console::WriteLine("Hello, world!");

	return 0;
}
```

# マネージドクラス

## クラス定義とインスタンス生成

`ref class` でクラス定義。インスタンスの変数は`^`をつけ、インスタンス生成には`gcnew`を用いる。

```C++
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`を使う。

```C++
#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`はカスタムのゲッター・セッターを定義している。

```C++
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`にもかけられる。

```C#
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"}
}
```

# マネージ配列

```c++
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


```c++
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型が必要です」と表示されるが、ビルドは正常に通り、問題なく動作する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/29000189-845b-cbb9-859a-1be3df5c4492.png)


# その他

備忘したい事項が出てきたら随時追記予定。

# 参考

C++/CLIの入門的な部分：
[C++/CLI入門 - WisdomSoft](http://www.wisdomsoft.jp/371.html)

C++/CLIのマネージドクラスでのプロパティについて：
[C++/CLI TIps：プロパティ](http://vene.wankuma.com/prog/CppCli_Property.aspx)