C# 側から使用する C++/CLI 側の関数の引数の型について

# 本記事について

C++/CLI で作成したクラスライブラリを C# から使用する際に、C++/CLI 側のクラス定義と C# 側の呼び出し処理で引数の型が不一致となり使用できないことがありましたので、忘備録として本記事を作成しました。

# 本記事の作成環境

* Windows 11 Home
* Visual Studio Community 2022

# C# 側から使用する C++/CLI 側の関数の引数の型

C# 側から呼び出す関数に対する C++/CLI 側での関数の引数の型について、参照クラス、値クラスごとにまとめます。

## 参照クラス

C++/CLI でクラスを ```ref class```、または ```ref struct``` として定義した場合、そのクラスは参照クラスとなります。
参照クラスのインスタンスを関数の引数として渡す場合は、原則として参照渡しとなります。

C# でクラス ```class``` として定義されている .NET Framework の標準ライブラリも本ケースに該当します。

### In パラメータで値を受け取り、戻り値で値を返却する関数

参照クラスを関数の In パラメータとして渡す場合は、引数の型は参照クラスのハンドル型 ```className ^``` となります。
参照クラスのハンドル型で渡された引数は参照渡しとなるため、関数内で行った値の変更が関数外に作用します。意図しない値の変更を抑止したい場合は、```const``` キーワードを使用します。
返却値も同様に参照クラスのハンドル型 ```className ^``` となります。

```cpp:RCoordinate.h
static RCoordinate^ Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2);
```

参照クラスのハンドル型で渡された引数には、アロー演算子 ```->``` を使用してアクセスします。
また、返却値は ```gcnew``` を使用してマネージドヒープ上にインスタンスを生成して返却します。

```cpp:RCoordinate.cpp
RCoordinate^ RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2) {
    return gcnew RCoordinate(
        coordinate1->_latitude + coordinate2->_latitude,
        coordinate1->_longitude + coordinate2->_longitude,
        coordinate1->_altitude + coordinate2->_altitude);
}
```

この関数の C# 側の呼び出し処理は以下のようになります。

```cs
CliLib.RCoordinate rCoordinate1 = new CliLib.RCoordinate(1.0, 2.0, 3.0);
CliLib.RCoordinate rCoordinate2 = new CliLib.RCoordinate(4.0, 5.0, 6.0);
CliLib.RCoordinate rCoordinate3 = CliLib.RCoordinate.Add(rCoordinate1, rCoordinate2);
```

### Out パラメータの値を変更する関数

参照クラスを関数の Out パラメータとして渡す場合は、引数の型は参照クラスのハンドル型 ```className ^``` となります。
参照クラスのハンドル型で渡された引数は参照渡しとなるため、関数内で行った値の変更が関数外に作用します。

```C#:RCoordinate.h
static void Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^ coordinate);
```

参照クラスのハンドル型で渡された引数には、アロー演算子 ```->``` を使用してアクセスします。

```C#:RCoordinate.cpp
void RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^ coordinate) {
    coordinate->_latitude = coordinate1->_latitude + coordinate2->_latitude;
    coordinate->_longitude = coordinate1->_longitude + coordinate2->_longitude;
    coordinate->_altitude = coordinate1->_altitude + coordinate2->_altitude;
}
```

この関数の C# 側の呼び出し処理は以下のようになります。

```cs
CliLib.RCoordinate rCoordinate1 = new CliLib.RCoordinate(1.0, 2.0, 3.0);
CliLib.RCoordinate rCoordinate2 = new CliLib.RCoordinate(4.0, 5.0, 6.0);
CliLib.RCoordinate rCoordinate4 = new CliLib.RCoordinate();
CliLib.RCoordinate.Add(rCoordinate1, rCoordinate2, rCoordinate4);
```

### 関数内で生成したインスタンスを Out パラメータで受け取る関数

関数内でマネージドヒープ上に生成した参照クラスのインスタンスを関数の Out パラメータとして受け取る場合は、引数の型は参照クラスの追跡参照型 ```className ^%``` となります。
参照クラスの追跡参照型で渡された引数はハンドルの参照渡しとなるため、関数内で行ったハンドルの変更が関数外に作用します。

```C#:RCoordinate.h
static void Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^% coordinate);
```

参照クラスの追跡参照型で渡された引数には、関数内で ```gcnew``` を使用して生成したインスタンスのハンドルを代入します。

```C#:RCoordinate.cpp
void RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^% coordinate) {
    coordinate = gcnew RCoordinate(
        coordinate1->_latitude + coordinate2->_latitude,
        coordinate1->_longitude + coordinate2->_longitude,
        coordinate1->_altitude + coordinate2->_altitude);
}
```

この関数の C# 側の呼び出し処理は以下のようになります。
Out パラメータには ```ref``` キーワードを使用します。

```cs
CliLib.RCoordinate rCoordinate1 = new CliLib.RCoordinate(1.0, 2.0, 3.0);
CliLib.RCoordinate rCoordinate2 = new CliLib.RCoordinate(4.0, 5.0, 6.0);
CliLib.RCoordinate? rCoordinate5 = null;
CliLib.RCoordinate.Add(rCoordinate1, rCoordinate2, ref rCoordinate5);
```

## 値クラス

C++/CLI でクラスを ```value class```、または ```value struct``` として定義した場合、そのクラスは値クラスとなります。
値クラスのインスタンスを関数の引数として渡す場合は、原則として値渡しとなります。

C# で構造体 ```struct``` 、列挙型 ```enum``` として定義されている .NET Framework の標準ライブラリ、およびプリミティブ型も本ケースに該当します。

### In パラメータで値を受け取り、戻り値で値を返却する関数

値クラスを関数の In パラメータとして渡す場合は、引数の型は値クラスの型 ```className``` となります。
値クラスの型で渡された引数は値渡しとなるため、関数内で行った値の変更が関数外に作用しません。
返却値も同様に値クラスの型  ```className``` となります。

```C#:VCoordinate.h
static VCoordinate Add(VCoordinate coordinate1, VCoordinate coordinate2);
```

値クラスの型で渡された引数には、ドット演算子 ```.``` を使用してアクセスします。
また、返却値は ```gcnew``` は使用せずスタック上にインスタンスを生成します。

```C#:VCoordinate.cpp
VCoordinate VCoordinate::Add(VCoordinate coordinate1, VCoordinate coordinate2) {
    return VCoordinate(
        coordinate1._latitude + coordinate2._latitude,
        coordinate1._longitude + coordinate2._longitude,
        coordinate1._altitude + coordinate2._altitude);
}
```

この関数の C# 側の呼び出し処理は以下のようになります。

```cs
CliLib.VCoordinate vCoordinate1 = new CliLib.VCoordinate(1.5, 2.5, 3.5);
CliLib.VCoordinate vCoordinate2 = new CliLib.VCoordinate(4.5, 5.5, 6.5);
CliLib.VCoordinate vCoordinate3 = CliLib.VCoordinate.Add(vCoordinate1, vCoordinate2);
```

### Out パラメータの値を変更する関数

値クラスを関数の Out パラメータとして渡す場合は、引数の型は値クラスの参照型 ```className %``` となります。
値クラスの参照型で渡された引数は参照渡しとなるため、関数内で行った値の変更が関数外に作用します。

```C#:VCoordinate.h
static void Add(VCoordinate coordinate1, VCoordinate coordinate2, VCoordinate% coordinate);
```

値クラスの参照型で渡された引数には、ドット演算子 ```.``` を使用してアクセスします。

```C#:VCoordinate.cpp
void VCoordinate::Add(VCoordinate coordinate1, VCoordinate coordinate2, VCoordinate% coordinate) {
    coordinate._latitude = coordinate1._latitude + coordinate2._latitude;
    coordinate._longitude = coordinate1._longitude + coordinate2._longitude;
    coordinate._altitude = coordinate1._altitude + coordinate2._altitude;
}
```

この関数の C# 側の呼び出し処理は以下のようになります。
Out パラメータには ```ref``` キーワードを使用します。

```cs
CliLib.VCoordinate vCoordinate1 = new CliLib.VCoordinate(1.5, 2.5, 3.5);
CliLib.VCoordinate vCoordinate2 = new CliLib.VCoordinate(4.5, 5.5, 6.5);
CliLib.VCoordinate vCoordinate4 = new CliLib.VCoordinate();
CliLib.VCoordinate.Add(vCoordinate1, vCoordinate2, ref vCoordinate4);
```

# ソースコード全体

本記事で作成したソースコードを以下に記載します。

## RCoordinate クラス（C++/CLI：参照クラス）

### ヘッダファイル

```cpp:RCoordinate.h
#pragma once

namespace CliLib {
    value struct VCoordinate;

    public ref struct RCoordinate {
        RCoordinate();
        RCoordinate(double latitude, double longitude, double altitude);
        RCoordinate(const RCoordinate^ other);

        static explicit operator VCoordinate(RCoordinate^ coordinate);

        property double Latitude {
            double get();
            void set(double latitude);
        }

        property double Longitude {
            double get();
            void set(double longitude);
        }

        property double Altitude {
            double get();
            void set(double altitude);
        }

        static RCoordinate^ Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2);
        static void Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^ coordinate);
        static void Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^% coordinate);

        System::String^ ToString() override;

    private:
        double _latitude;
        double _longitude;
        double _altitude;
    };
}
```

### ソースファイル

```cpp:RCoordinate.cpp
#include "VCoordinate.h"
#include "RCoordinate.h"

using namespace CliLib;

RCoordinate::RCoordinate()
    : RCoordinate(0.0, 0.0, 0.0) {
}

RCoordinate::RCoordinate(double latitude, double longitude, double altitude)
    : _latitude(latitude), _longitude(longitude), _altitude(altitude) {
}

RCoordinate::RCoordinate(const RCoordinate^ other)
    : RCoordinate(other->_latitude, other->_longitude, other->_altitude) {
}

RCoordinate::operator VCoordinate(RCoordinate^ coordinate) {
    return VCoordinate(coordinate->Latitude, coordinate->Longitude, coordinate->Altitude);
}

double RCoordinate::Latitude::get() {
    return _latitude;
}

void RCoordinate::Latitude::set(double latitude) {
    _latitude = latitude;
}

double RCoordinate::Longitude::get() {
    return _longitude;
}

void RCoordinate::Longitude::set(double longitude) {
    _longitude = longitude;
}

double RCoordinate::Altitude::get() {
    return _altitude;
}

void RCoordinate::Altitude::set(double altitude) {
    _altitude = altitude;
}

RCoordinate^ RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2) {
    return gcnew RCoordinate(
        coordinate1->_latitude + coordinate2->_latitude,
        coordinate1->_longitude + coordinate2->_longitude,
        coordinate1->_altitude + coordinate2->_altitude);
}

void RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^ coordinate) {
    coordinate->_latitude = coordinate1->_latitude + coordinate2->_latitude;
    coordinate->_longitude = coordinate1->_longitude + coordinate2->_longitude;
    coordinate->_altitude = coordinate1->_altitude + coordinate2->_altitude;
}

void RCoordinate::Add(const RCoordinate^ coordinate1, const RCoordinate^ coordinate2, RCoordinate^% coordinate) {
    coordinate = gcnew RCoordinate(
        coordinate1->_latitude + coordinate2->_latitude,
        coordinate1->_longitude + coordinate2->_longitude,
        coordinate1->_altitude + coordinate2->_altitude);
}

System::String^ RCoordinate::ToString() {
    return System::String::Format(
        "({0:0.000000}, {1:0.000000}, {2:0.000000})",
        Latitude, Longitude, Altitude);
}
```

## VCoordinate クラス（C++/CLI：値クラス）

### ヘッダファイル

```cpp:VCoordinate.h
#pragma once

namespace CliLib {
    ref struct RCoordinate;

    public value struct VCoordinate {
        VCoordinate(double latitude, double longitude, double altitude);

        static explicit operator RCoordinate ^ (VCoordinate coordinate);

        property double Latitude {
            double get();
            void set(double latitude);
        }

        property double Longitude {
            double get();
            void set(double longitude);
        }

        property double Altitude {
            double get();
            void set(double altitude);
        }

        static VCoordinate Add(VCoordinate coordinate1, VCoordinate coordinate2);
        static void Add(VCoordinate coordinate1, VCoordinate coordinate2, VCoordinate% coordinate);

        System::String^ ToString() override;

    private:
        double _latitude;
        double _longitude;
        double _altitude;
    };
}
```

### ソースファイル

```cpp:VCoordinate.cpp
#include "RCoordinate.h"
#include "VCoordinate.h"

using namespace CliLib;

VCoordinate::VCoordinate(double latitude, double longitude, double altitude)
    : _latitude(latitude), _longitude(longitude), _altitude(altitude) {
}

VCoordinate::operator RCoordinate ^ (VCoordinate coordinate) {
    return gcnew RCoordinate(coordinate.Latitude, coordinate.Longitude, coordinate.Altitude);
}

double VCoordinate::Latitude::get() {
    return _latitude;
}

void VCoordinate::Latitude::set(double latitude) {
    _latitude = latitude;
}

double VCoordinate::Longitude::get() {
    return _longitude;
}

void VCoordinate::Longitude::set(double longitude) {
    _longitude = longitude;
}

double VCoordinate::Altitude::get() {
    return _altitude;
}

void VCoordinate::Altitude::set(double altitude) {
    _altitude = altitude;
}

VCoordinate VCoordinate::Add(VCoordinate coordinate1, VCoordinate coordinate2) {
    return VCoordinate(
        coordinate1._latitude + coordinate2._latitude,
        coordinate1._longitude + coordinate2._longitude,
        coordinate1._altitude + coordinate2._altitude);
}

void VCoordinate::Add(VCoordinate coordinate1, VCoordinate coordinate2, VCoordinate% coordinate) {
    coordinate._latitude = coordinate1._latitude + coordinate2._latitude;
    coordinate._longitude = coordinate1._longitude + coordinate2._longitude;
    coordinate._altitude = coordinate1._altitude + coordinate2._altitude;
}

System::String^ VCoordinate::ToString() {
    return System::String::Format(
        "({0:0.000000}, {1:0.000000}, {2:0.000000})",
        Latitude, Longitude, Altitude);
}
```