1
1

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

Last updated at Posted at 2023-04-03

UnityでC++ライブラリを使用することがあったため、リンク先の記事で説明がなかった部分について深掘りしてみた。
C++ライブラリ(DLL)をUnity(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);
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++では宣言をヘッダーファイルに分けて記述する必要がある。

ファイル名

ファイル名はクラス名をスネークケースにするのが無難そう。

拡張子

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

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

pragma once

前提知識として、ヘッダーファイルのコードをソースファイルで利用するために

export_test.cpp
#include "export_test.hpp"

のようにソースファイル内でincludeする必要がある。

pragma onceキーワドについて、例えばヘッダーにpragma onceを入れていないとソースファイルで

export_test.cpp
#include "export_test.hpp"
#include "export_test.hpp"

とすると、二回目のincludeでコードが重複するためエラーが発生する。
そこで、ヘッダーにpragma onceを入れておけば、この二回目includeは自動スキップしてくれるためエラーが防げる。

defineと__declspec

defineでEXPORTを定義している。

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

定義したEXPORTを使用している。

export_test.hpp
extern "C" EXPORT ExportTest* createExportTest();

define

#define マクロ名 置換テキスト
のように使える。例えば

example.cpp
#define PI 3.14

// PIを使用して円周長を計算する
double radius = 5.0;
double circleLength = 2 * radius * PI;

のようにも使える。

__declspec

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

__declspecの使用例

  • エクスポート側
export.hpp
// 関数をエクスポート 
__declspec(dllexport) int Add(int a, int b);
export.cpp
#include "export.hpp"

__declspec(dllexport) int Add(int a, int b)
{
    return a + b;
}
  • インポート側で使用
import.cpp
#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(); 

そのため、

export_test.hpp
extern "C" EXPORT ExportTest* createExportTest();
export_test.cpp
EXPORT ExportTest* createExportTest()
{
    return new ExportTest();
}

上記メソッドの返り値はExportTest型のオブジェクトではなくポインタ型となる。

アドレスについて

//ExportTestのインスタンスを作成
ExportTest exportTest;
//ポインタexportTestPtrを宣言
ExportTest *exportTestPtr;
//ポインタにアドレスを代入
exportTestPtr = &exportTest;

このように&演算子をつけることでアドレスを取得できる。

注意すべきは

// C++だと下記はエラー
ExportTest exportTest = new ExportTest();

と、C#のようにnewしなくても初期化される点。コンストラクタもこの時点で呼び出される。

ポインタ渡し

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

C#で値渡しと参照渡しを使うが、上記はポインタ渡し。
ポインタ渡しは参照渡しとの違いが分かりにくいので注意。

cf:
値渡し、ポインタ渡し、参照渡しの違い
C++ポインタまとめ

その他

delete

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

C++では明示的にメモリを解放する必要があるためdelete演算子を使って解放している。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1