Unityのネイティブプラグインをはじめました
昨日に引き続き、今日はネイティブプラグインを扱います。
ここではUnity 2018 1.3f、Visual Studio 2017を使用しています。
おかげさまで絶好調です。
ネイティブプラグインとは
多分公式マニュアルを見た方が早いでしょうか?
docs.unity3d.com
既存のC,C++で書かれたライブラリを、 C#で扱うための仕組みです。
Visual Studioなど他のコンパイラでdllファイルを作成し、Unityでこのdllの関数を呼び出します。
ネイティブプラグインの作成
dllの作成
まずはVisual Studio 2017でdllを作成するプロジェクトを作成します。
Visual C++ > Windows デスクトップ > ダイナミック リンク ライブラリ(.dll)のプロジェクトを作成します。
初期状態では、以下のようなプロジェクトになっています。
ここでは「NativePluginSample.cpp」のみを編集します。
さらにdllをincludeするためのヘッダーファイル「NativePluginSample.h」をプロジェクトに追加します。
NativePluginSample.h
#pragma once
#ifdef NATIVEPLUGINSAMPLE_EXPORTS
#define SAMPLE_API __declspec(dllexport)
#else
#define SAMPLE_API __declspec(dllimport)
#endif
extern "C" {
// 値渡し
SAMPLE_API int SampleAPIInt(int i);
SAMPLE_API float SampleAPIFloat(float f);
SAMPLE_API double SampleAPIDouble(double d);
// 参照渡し
SAMPLE_API void SampleAPIInt2(int& i);
SAMPLE_API void SampleAPIFloat2(float& f);
SAMPLE_API void SampleAPIDouble2(double& d);
// 配列の参照渡し
SAMPLE_API void SampleAPIIntArray(int intArray[], int intArraySize);
SAMPLE_API void SampleAPILongArray(long longArray[], int longArraySize);
SAMPLE_API void SampleAPIFloatArray(float floatArray[], int floatArraySize);
SAMPLE_API void SampleAPIDoubleArray(double doubleArray[], int doubleArraySize);
// 文字列
SAMPLE_API const char* SampleAPIString1();
SAMPLE_API const char* SampleAPIString2(const char* str);
}
NativePluginSample.cpp
#include "stdafx.h"
#include "stdlib.h"
#include "NativePluginSample.h"
SAMPLE_API int SampleAPIInt(int i)
{
return 123 + i;
}
SAMPLE_API float SampleAPIFloat(float f)
{
return 123.456f + f;
}
SAMPLE_API double SampleAPIDouble(double d)
{
return 123.456 + d;
}
SAMPLE_API void SampleAPIInt2(int& i)
{
i = 123 + i;
}
SAMPLE_API void SampleAPIFloat2(float& f)
{
f = 123.456f + f;
}
SAMPLE_API void SampleAPIDouble2(double& d)
{
d = 123.456 + d;
}
SAMPLE_API void SampleAPIIntArray(int intArray[], int intArraySize)
{
for (int i = 0; i < intArraySize; i++)
{
intArray[i] = i;
}
}
SAMPLE_API void SampleAPILongArray(long longArray[], int longArraySize)
{
for (int i = 0; i < longArraySize; i++)
{
longArray[i] = i;
}
}
SAMPLE_API void SampleAPIFloatArray(float floatArray[], int floatArraySize)
{
for (int i = 0; i < floatArraySize; i++)
{
floatArray[i] = 0.5f + i;
}
}
SAMPLE_API void SampleAPIDoubleArray(double doubleArray[], int doubleArraySize)
{
for (int i = 0; i < doubleArraySize; i++)
{
doubleArray[i] = 0.5 + i;
}
}
SAMPLE_API char* MallocString(const char* str)
{
size_t bufSize = strlen(str) + 1;
char* buf = (char*)malloc(bufSize);
strcpy_s(buf, bufSize, str);
return buf;
}
SAMPLE_API const char* SampleAPIString1()
{
return MallocString("Hello!");
}
SAMPLE_API const char* SampleAPIString2(const char* str)
{
// 文字列の後ろに"Hello!"をくっ付けて返す
const int bufferSize = 256;
static char buffer[bufferSize];
const char hello[] = "Hello!";
strcpy_s(buffer, bufferSize, str);
size_t n = strnlen(buffer, bufferSize);
strcpy_s(buffer + n, bufferSize - 7, hello);
return MallocString(buffer);
}
このプロジェクトを作成したときに、プロジェクトのプロパティ内で、プリプロセッサの定義に NATIVEPLUGINSAMPLE_EXPORTS
が定義されています。これを利用して dllexport 属性またはdllimport 属性を使用出来ます。
正しくdllexport属性が使用できていれば、ビルド時にインポートライブラリ(*.libファイル)が*.dllファイルと共に作成されます。
注意するべき点は、const char* 型をC#側でstring型として扱う場合、malloc()でヒープ領域にメモリを確保して返す必要があることです。うっかりスタック領域を返してしまうと、最悪Unity Editorごと落ちます。malloc()で確保したメモリは、stringが解放されるタイミングで開放されます。
ポインタを扱う場合、C#側ではIntPtr型で扱えますが、マーシャライズなどを行う必要があるため今回説明は省略します。
dllのテストプログラムの作成
dllが正しく作られたかテストを行うために、先ほどのソリューションに追加する形で、Windows コンソールアプリケーションを作成します。
テストプログラムのプロジェクトが追加されたら、テストプログラムのプロジェクト側の「参照」に、先ほどのdllのプロジェクトを追加します。
構成はこのようになります。テストプログラムの方をスタートアッププロジェクトに設定しておけば、デバッグ実行を行うことが出来ます。
インクルードディレクトリにdllプロジェクトのヘッダファイルが入ったフォルダを追加しておきます。
以下のようなチェック用プログラムを作成しました。
NativePluginSampleTest.cpp
#include "stdafx.h"
#include "stdlib.h"
#include "NativePluginSample.h"
int main()
{
printf("int: %d\n", SampleAPIInt(111));
printf("float: %f\n", SampleAPIFloat(111.111f));
printf("double: %f\n", SampleAPIDouble(111.111));
int i = 222;
float f = 222.f;
double d = 222.0;
SampleAPIInt2(i);
SampleAPIFloat2(f);
SampleAPIDouble2(d);
printf("int: %d\n", i);
printf("float: %f\n", f);
printf("double: %f\n", d);
int is[3] = { 111, 222, 333 };
long ls[3] = { 111, 222, 333 };
float fs[3] = { 111.f, 222.f, 333.f };
double ds[3] = { 111.0, 222.0, 333.0 };
SampleAPIIntArray(is, 3);
SampleAPILongArray(ls, 3);
SampleAPIFloatArray(fs, 3);
SampleAPIDoubleArray(ds, 3);
printf("int: %d,%d,%d\n", is[0], is[1], is[2]);
printf("long: %d,%d,%d\n", ls[0], ls[1], ls[2]);
printf("float: %f,%f,%f\n", fs[0], fs[1], fs[2]);
printf("double: %f,%f,%f\n", ds[0], ds[1], ds[2]);
const char st[] = "MyTest ";
const char* p1 = SampleAPIString1();
const char* p2 = SampleAPIString2(st);
printf("string1: %s\n", p1);
printf("string2: %s\n", p2);
free((void*)p1);
free((void*)p2);
return 0;
}
このプログラムを実行すると、dllの関数が呼び出されていることが分かります。
Unity側での使用
Unityのプロジェクトを作成します。
”Plugins”フォルダを作成し、中にReleaseビルドしたdllファイルを入れます。
(64bit版と32bit版を用意し、Platform settingsで振り分けます。)
C#の関数にdllの関数を割り当てます。
lib.cs
using System.Runtime.InteropServices;
public class Lib {
// 値渡し
[DllImport("NativePluginSample")]
public static extern int SampleAPIInt(int i);
[DllImport("NativePluginSample")]
public static extern float SampleAPIFloat(float f);
[DllImport("NativePluginSample")]
public static extern double SampleAPIDouble(double d);
// 参照渡し
[DllImport("NativePluginSample")]
public static extern void SampleAPIInt2(ref int i);
[DllImport("NativePluginSample")]
public static extern void SampleAPIFloat2(ref float f);
[DllImport("NativePluginSample")]
public static extern void SampleAPIDouble2(ref double d);
// 配列の参照渡し
[DllImport("NativePluginSample")]
public static extern void SampleAPIIntArray(int[] intArray, int intArraySize);
[DllImport("NativePluginSample")]
public static extern void SampleAPILongArray(int[] longArray, int longArraySize);
[DllImport("NativePluginSample")]
public static extern void SampleAPIFloatArray(float[] floatArray, int floatArraySize);
[DllImport("NativePluginSample")]
public static extern void SampleAPIDoubleArray(double[] doubleArray, int doubleArraySize);
// 文字列
[DllImport("NativePluginSample")]
public static extern string SampleAPIString1();
[DllImport("NativePluginSample")]
public static extern string SampleAPIString2(string str);
}
テストプログラムを作成し、適当なオブジェクトにアタッチして実行します。
内容は先ほどのC++版のほぼ等価です。
TestObject.cpp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestObject : MonoBehaviour {
// Use this for initialization
void Start () {
Debug.Log("int: " + Lib.SampleAPIInt(111));
Debug.Log("float: " + Lib.SampleAPIFloat(111.111f));
Debug.Log("double: " + Lib.SampleAPIDouble(111.111));
int i = 222;
float f = 222.0f;
double d = 222.0;
Lib.SampleAPIInt2(ref i);
Lib.SampleAPIFloat2(ref f);
Lib.SampleAPIDouble2(ref d);
Debug.Log("int: " + i);
Debug.Log("float: " + f);
Debug.Log("double: " + d);
int[] i_s = new int[] { 111, 222, 333 };
int[] l_s = new int[] { 111, 222, 333 };
float[] f_s = new float[] { 111.0f, 222.0f, 333.0f };
double[] d_s = new double[] { 111.0, 222.0, 333.0 };
Lib.SampleAPIIntArray(i_s, i_s.Length);
Lib.SampleAPILongArray(l_s, l_s.Length);
Lib.SampleAPIFloatArray(f_s, f_s.Length);
Lib.SampleAPIDoubleArray(d_s, d_s.Length);
Debug.LogFormat("int: {0},{1},{2}", i_s[0], i_s[1], i_s[2]);
Debug.LogFormat("long: {0},{1},{2}", l_s[0], l_s[1], l_s[2]);
Debug.LogFormat("float: {0},{1},{2}", f_s[0], f_s[1], f_s[2]);
Debug.LogFormat("double: {0},{1},{2}", d_s[0], d_s[1], d_s[2]);
string st = "MyTest ";
string s1 = Lib.SampleAPIString1();
string s2 = Lib.SampleAPIString2(st);
Debug.Log("string1: " + s1);
Debug.Log("string2: " + s2);
}
}
動作が確認できました。