#0.はじめに
環境を載せておきます。
- Visual Studio Community 2017 Version 15.9.4
- Unity 2018.3.0f2
#1.C++をDLL化する
基本的にUnityでC++を使うにはDLL化する必要があります。
VisualStudioから新しいプロジェクト->Visual C++->Windowsデスクトップ->Windowsデスクトップウィザード
を選択してください。
ダイナミックリンクライブラリ(DLL) を選択しないのは余計なファイルが大量に作られるため
(DLLのエントリポイントやヘッダ)
今回はaddというプロジェクト名にしています。
ウィザードでは
- アプリケーションの種類をダイナミックリンクライブラリ(.dll)
- 空のプロジェクトにチェック
- 残りのチェックを全部外す
ソースファイルを追加して次のコードを書きます。
extern "C" __declspec(dllexport) int __stdcall add_function(int a, int b) {
return a + b;
}
*ソースファイルの1行目は念のため開けてください
関数は引数に2つの変数を取り、足して返すだけのもの。
extern "C" __declspec(dllexport) __stdcallの説明は後々
DLLを作るときは基本的にヘッダもセットで作成することが多いですが
今回はUnityのみでの使用なので作りませんでした(汎用性を考えるなら作った方がいいです)
それではビルドしてください
ビルド->バッチビルドでx86とx64をまとめてビルドできます。
DLLの場所はプロジェクトフォルダの./Releaseにx86が./x64/Releaseにx64のdllがあります。
#2.extern "C" __declspec(dllexport) __stdcallとはなんなのか
興味のない方は飛ばしてください、しらなくても問題ないです。
まず__stdcallの説明から
__stdcallは呼び出し規約と呼ばれるもので、引数のスタック(変数)の処理に関する決め事みたいなもの
__stdcallがついた関数は、スタックの破棄(巻き戻し)は呼び出された側(つまりDLL側)が行います。
ほかにも __cdeclという呼び出し規約もあり、こちらは呼び出した側(つまりEXE側)が行います。
この呼び出し規約がDLLとEXEで一致してないとスタックの破棄がうまくいかないので、
スタックオーバーフローの原因になります。
x86 Cコンパイラではデフォルトで__cdecl、C#はデフォルトで__stdcallでDLLを呼び出すため
どちらかを設定しなおす必要があります、めんどくさいですが必ずこの構文を明記しましょう。
__declspec(dllexport)構文はDLLのエクスポートセクションに関数名を書き込むためのもので、
これによってEXE側がこの関数を呼び出せるようになります。
仮にもしこの構文がないとDLL内部で使われる関数だと解釈されます。
extern "C" は C++の名前装飾に対処するためのものです。
先ほどエクスポートセクションに関数名が書かれると書きましたが
そのままの関数名で書かれるわけではなく、謎の文字列が付け加えられた関数名になり
呼び出す側はそんなこと知らないのでextern "C"がないと関数名の検索に失敗します。
この名前の変更を名前装飾、マングリングなんて言ったりします。
C++のオーバーロードの機能等を実現するこのような動作をします。
#3.UnityからDLLを呼び出す
AssetsにPluginsフォルダを作成、x86とx86_64フォルダ作成し
先ほどのDLLをぶっこみます。
add.dllのInspector->PlatformsettingsでUnityのアイコンタブから
x86_64のdllならCPUをx86_64にx86ならx86に設定します。
続いてとなりのタブのからx86_64のdllならx_86_x64にチェック
x86なら全部にチェックします(互換性があるため)
Architectureの設定はFile->Build Settingsからできます。
適当な空のオブジェクトを作成し、scriptを追加します。
using UnityEngine;
using System.Runtime.InteropServices;
static class DLL
{
[DllImport("add", CallingConvention = CallingConvention.StdCall)]
public static extern int add_function(int a, int b);
}
public class dll_exector : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log(DLL.add_function(1, 1));
}
}
DllImport属性でdllを指定、CallingConventionで呼び出し規約を明記できますが
デフォルトでstdcallなのでなくてもかまいません。
修飾子であるexternとstaticはadd_functionが外部にある既存の関数ということなので必須です。