"C++ライブラリ（DLL）をUnity（C#）向けに作成して利用するシンプルな方法"の深掘り

UnityでC++ライブラリを使用することがあったため、リンク先の記事で説明がなかった部分について深掘りしてみた。
[C++ライブラリ（DLL）をUnity（C#）向けに作成して利用するシンプルな方法](https://zenn.dev/meson/articles/make-dll-for-unity#c%2B%2B%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%AF%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%82%92%E7%B5%8C%E7%94%B1%E3%81%97%E3%81%A6%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B)

C++なんもわからん状態から書いたので、あとで編集追記することになる予感。

# 参照コード
記事内のコードをファイル名だけ編集したもの。
~~~C++:export_test.hpp
#pragma once

#ifdef DLLFORUNITY_EXPORTS
#   define EXPORT __declspec(dllexport)
#else
#   define EXPORT __declspec(dllimport)
#endif

class ExportTest
{
public:
    int TestFunc(int a);
};

extern "C" EXPORT ExportTest* createExportTest();
extern "C" EXPORT void freeExportTest(ExportTest* instance);
extern "C" EXPORT int getResult(ExportTest* instance, int a);
~~~

~~~C++:export_test.cpp
#include "pch.h"
#include "export_test.hpp"

int ExportTest::TestFunc(int a)
{
    return a + 5;
}

EXPORT ExportTest* createExportTest()
{
    return new ExportTest();
}

EXPORT void freeExportTest(ExportTest* instance)
{
    delete instance;
}

EXPORT int getResult(ExportTest* instance, int a)
{
    return instance->TestFunc(a);
}
~~~

# ヘッダーファイル
CやC++と比べてunityで使うC#は高レベルな言語のため宣言と実装を自動で分離してくれている。そのためC++では宣言をヘッダーファイルに分けて記述する必要がある。

### ファイル名
ファイル名は[クラス名をスネークケースにするのが無難そう。](https://qiita.com/shirakawa4756/items/55b509fb56cb1bb0c9a4#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D-google)

### 拡張子
ソースファイルの拡張子はそのまま.cpp（シープラプラ）でよいが、ヘッダーファイルの拡張子は.hpp（ヘッダープラプラ）とする。
ちなみにC言語のヘッダーファイルはそのまま.h(ヘッダー)。
.hのままでも実装上は問題ないみたいだが.hppとしてC++な点を強調できる。

### 例
記事内ではExportTestクラスのファイルなので、export_test.hppとexport_test.cppにした。

# pragma once
前提知識として、ヘッダーファイルのコードをソースファイルで利用するために
~~~c++:export_test.cpp
#include "export_test.hpp"
~~~
のようにソースファイル内でincludeする必要がある。

pragma onceキーワドについて、例えばヘッダーにpragma onceを入れていないとソースファイルで
~~~c++:export_test.cpp
#include "export_test.hpp"
#include "export_test.hpp"
~~~
とすると、二回目のincludeでコードが重複するためエラーが発生する。
そこで、ヘッダーにpragma onceを入れておけば、この二回目includeは自動スキップしてくれるためエラーが防げる。

# defineと__declspec
defineでEXPORTを定義している。
~~~c++:export_test.hpp
#ifdef DLLFORUNITY_EXPORTS
#   define EXPORT __declspec(dllexport)
#else
#   define EXPORT __declspec(dllimport)
#endif
~~~
定義したEXPORTを使用している。
~~~c++:export_test.hpp
extern "C" EXPORT ExportTest* createExportTest();
~~~
### define
#define マクロ名 置換テキスト
のように使える。例えば
~~~c++:example.cpp
#define PI 3.14

// PIを使用して円周長を計算する
double radius = 5.0;
double circleLength = 2 * radius * PI;
~~~
のようにも使える。

### __declspec

Windowsプラットフォームで動作するダイナミックリンクライブラリ（DLL）のエクスポートとインポートに使用されるC++キーワード。
引数でエクスポートとインポートを使い分ける。

### __declspecの使用例
* エクスポート側
~~~c++:export.hpp
// 関数をエクスポート 
__declspec(dllexport) int Add(int a, int b);
~~~
~~~c++:export.cpp
#include "export.hpp"

__declspec(dllexport) int Add(int a, int b)
{
    return a + b;
}
~~~
* インポート側で使用
~~~c++:import.cpp
#include <windows.h>
#include <iostream>

// 関数のプロトタイプを宣言
__declspec(dllimport) int Add(int a, int b);

int main()
{
    // DLLから関数を呼び出す
    int result = Add(5, 3);
    ...
}
~~~

# アスタリスク「*」
### ExportTest*
~~~c++:
ExportTest* exportTestPtr;
ExportTest * exportTestPtr;
ExportTest *exportTestPtr;
~~~

どれも同じでExportTest型のアドレスを持つ変数exportTestPtrを宣言している。
このアドレスを持つ変数のことをポインタという。

注意すべきはC++で下記はエラー。
~~~
ExportTest obj = new ExportTest(); 
~~~
ただしくは
~~~
ExportTest* p = new ExportTest(); 
~~~
そのため、

~~~c++:export_test.hpp
extern "C" EXPORT ExportTest* createExportTest();
~~~
~~~c++:export_test.cpp
EXPORT ExportTest* createExportTest()
{
    return new ExportTest();
}
~~~
上記メソッドの返り値はExportTest型のオブジェクトではなくポインタ型となる。

### アドレスについて
~~~c++
//ExportTestのインスタンスを作成
ExportTest exportTest;
//ポインタexportTestPtrを宣言
ExportTest *exportTestPtr;
//ポインタにアドレスを代入
exportTestPtr = &exportTest;
~~~
このように&演算子をつけることでアドレスを取得できる。

注意すべきは
~~~c++
// C++だと下記はエラー
ExportTest exportTest = new ExportTest();
~~~
と、C#のようにnewしなくても初期化される点。コンストラクタもこの時点で呼び出される。

### ポインタ渡し

~~~c++:export_test.cpp
EXPORT void freeExportTest(ExportTest* instance)
{
    delete instance;
}
~~~
C#で値渡しと参照渡しを使うが、上記はポインタ渡し。
ポインタ渡しは参照渡しとの違いが分かりにくいので注意。

cf:
[値渡し、ポインタ渡し、参照渡しの違い](https://qiita.com/lon9/items/e70e3b453cb485858062)
[C++ポインタまとめ](https://qiita.com/yokoto/items/5672ff20b63815728d90#%E5%80%A4%E6%B8%A1%E3%81%97)

# その他
### delete
~~~c++:export_test.cpp
EXPORT void freeExportTest(ExportTest* instance)
{
    delete instance;
}
~~~
C++では明示的にメモリを解放する必要があるためdelete演算子を使って解放している。