UnityでC++ライブラリを使用することがあったため、リンク先の記事で説明がなかった部分について深掘りしてみた。
C++ライブラリ(DLL)をUnity(C#)向けに作成して利用するシンプルな方法
C++なんもわからん状態から書いたので、あとで編集追記することになる予感。
参照コード
記事内のコードをファイル名だけ編集したもの。
#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);
#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++では宣言をヘッダーファイルに分けて記述する必要がある。
ファイル名
ファイル名はクラス名をスネークケースにするのが無難そう。
拡張子
ソースファイルの拡張子はそのまま.cpp(シープラプラ)でよいが、ヘッダーファイルの拡張子は.hpp(ヘッダープラプラ)とする。
ちなみにC言語のヘッダーファイルはそのまま.h(ヘッダー)。
.hのままでも実装上は問題ないみたいだが.hppとしてC++な点を強調できる。
例
記事内ではExportTestクラスのファイルなので、export_test.hppとexport_test.cppにした。
pragma once
前提知識として、ヘッダーファイルのコードをソースファイルで利用するために
#include "export_test.hpp"
のようにソースファイル内でincludeする必要がある。
pragma onceキーワドについて、例えばヘッダーにpragma onceを入れていないとソースファイルで
#include "export_test.hpp"
#include "export_test.hpp"
とすると、二回目のincludeでコードが重複するためエラーが発生する。
そこで、ヘッダーにpragma onceを入れておけば、この二回目includeは自動スキップしてくれるためエラーが防げる。
defineと__declspec
defineでEXPORTを定義している。
#ifdef DLLFORUNITY_EXPORTS
# define EXPORT __declspec(dllexport)
#else
# define EXPORT __declspec(dllimport)
#endif
定義したEXPORTを使用している。
extern "C" EXPORT ExportTest* createExportTest();
define
#define マクロ名 置換テキスト
のように使える。例えば
#define PI 3.14
// PIを使用して円周長を計算する
double radius = 5.0;
double circleLength = 2 * radius * PI;
のようにも使える。
__declspec
Windowsプラットフォームで動作するダイナミックリンクライブラリ(DLL)のエクスポートとインポートに使用されるC++キーワード。
引数でエクスポートとインポートを使い分ける。
__declspecの使用例
- エクスポート側
// 関数をエクスポート
__declspec(dllexport) int Add(int a, int b);
#include "export.hpp"
__declspec(dllexport) int Add(int a, int b)
{
return a + b;
}
- インポート側で使用
#include <windows.h>
#include <iostream>
// 関数のプロトタイプを宣言
__declspec(dllimport) int Add(int a, int b);
int main()
{
// DLLから関数を呼び出す
int result = Add(5, 3);
...
}
アスタリスク「*」
ExportTest*
ExportTest* exportTestPtr;
ExportTest * exportTestPtr;
ExportTest *exportTestPtr;
どれも同じでExportTest型のアドレスを持つ変数exportTestPtrを宣言している。
このアドレスを持つ変数のことをポインタという。
注意すべきはC++で下記はエラー。
ExportTest obj = new ExportTest();
ただしくは
ExportTest* p = new ExportTest();
そのため、
extern "C" EXPORT ExportTest* createExportTest();
EXPORT ExportTest* createExportTest()
{
return new ExportTest();
}
上記メソッドの返り値はExportTest型のオブジェクトではなくポインタ型となる。
アドレスについて
//ExportTestのインスタンスを作成
ExportTest exportTest;
//ポインタexportTestPtrを宣言
ExportTest *exportTestPtr;
//ポインタにアドレスを代入
exportTestPtr = &exportTest;
このように&演算子をつけることでアドレスを取得できる。
注意すべきは
// C++だと下記はエラー
ExportTest exportTest = new ExportTest();
と、C#のようにnewしなくても初期化される点。コンストラクタもこの時点で呼び出される。
ポインタ渡し
EXPORT void freeExportTest(ExportTest* instance)
{
delete instance;
}
C#で値渡しと参照渡しを使うが、上記はポインタ渡し。
ポインタ渡しは参照渡しとの違いが分かりにくいので注意。
cf:
値渡し、ポインタ渡し、参照渡しの違い
C++ポインタまとめ
その他
delete
EXPORT void freeExportTest(ExportTest* instance)
{
delete instance;
}
C++では明示的にメモリを解放する必要があるためdelete演算子を使って解放している。