1. imura

    Posted

    imura
Changes in title
+Windows で Unity の Native Plugin を作成する
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,150 @@
+以下の環境で説明する.
+
+- Windows 8.1
+- Visual Studio Community 2013
+- Unity 5.1.1f1 Personal
+
+# Unity の Native Plugin とは
+
+Unity には Plugin という,Unity 外部で作られたプログラムを呼び出して使うための仕組みがある.
+
+Unity の Plugin には Managed と Native の2種類がある.
+
+Managed Plugin は .NET フレームワーク上で作成され,機能が限定される(.NET libraryしか使えない)代わりに,Unity 内部の script とほとんど同じように扱える.
+
+Native Plugin はプラットフォーム依存である代わりにどのようなライブラリでも使うことができる.具体的には,DLL (Dynamic Link Library)を作成し,Unity の script から関数を呼び出す形で使用する.ただし Unity とは独立性が高く,Managed Plugin のようなシームレスなデバッグはできない.
+
+ここでは,Windows上でKinectから得られるデータをOpenCVで処理した結果をUnityで使いたいので,Native Plugin の使用を考える.
+
+## Native Plugin の制約
+
+Unity が64ビットバイナリで動作している場合,Native Plugin のDLLも64ビットバイナリで作成しなければならない.
+
+# Native Plugin を作る
+
+まずは,非常に簡単なプラグインを作って,感触を確かめる.ここでは64ビットバイナリであるとする.
+
+## DLL向けの開発環境設定
+
+Visual Studio Community 2013 でDLLを作成する.C++ を使って記述する.
+
+1. Visual Studio Community 2013 を起動する.
+2. 新しいプロジェクト をクリック.
+3. Win32 コンソール アプリケーション を選択する.プロジェクトを作る.ここでは sample-dll という名前にする.
+4. アプリケーションの設定がポイントである.アプリケーションの種類を「DLL」に,追加のオプションの「シンボルのエクスポート」をチェックし,「Security Development Lifecycle (SDL) チェック」のチェックを外す.
+
+64ビットバイナリを生成するように,構成マネージャーでプラットフォームを変更する.方法については http://qiita.com/imura/items/076196b50f105c3138f7 を参照.
+
+## 関数の作成
+
+関数は プロジェクト名.cpp (この例ではsample-dll.cpp)に記述する.変数,関数,クラスの例が既に記述されているため,同様に記述すればよい.具体的には,関数宣言の先頭に `SAMPLEDLL_API` を付加する.
+
+ここでは例として,int型の値を返す関数 `int CountUp(void);` を作る.最初の返値は0で,呼ぶたびに返値が1ずつ増える,という関数である.
+
+```cpp
+int counter = 0;
+
+SAMPLEDLL_API int CountUp(void)
+{
+ return counter++;
+}
+```
+
+## ヘッダファイルへの記述: C++のネームマングリング
+
+関数のプロトタイプ宣言は プロジェクト名.h (この例では sample-dll.h)に記述する.
+
+C++で作成された関数は,コンパイル時に引数や返値の型を表す接頭/接尾辞が自動的に付加される(ネームマングリング)ため,ソースコードに書かれた関数名そのままではUnityから呼び出すことができない.ネームマングリングを避けるためには,ヘッダファイルの関数プロトタイプ宣言に,`extern "C"` を付ければよい.
+
+```cpp
+extern "C" {
+ SAMPLEDLL_API int CountUp(void);
+}
+```
+
+## DLLの作成
+
+ソースコードが記述できたら,アクティブソリューションの構成をReleaseに,プラットフォームをx64に変更して,ソリューションをビルドする.フォルダ `x64/Release` 以下に,`sample-dll.lib` および `sample-dll.dll` が作成される.
+
+# DLLのテスト
+
+生成したDLLは,一般に広く使われているライブラリと扱いは同じである.別のアプリケーションを作成することで,Unity で使う前に,テストをしてみることができる.ここではプロジェクト名を sample-dll-exec とし,新しく Win32 コンソール アプリケーション を作成する.
+
+テスト用アプリケーションも,64ビットバイナリで生成されるように,構成マネージャーでプラットフォームを変更する必要がある.
+
+Main.cpp として以下を入力する.
+
+```cpp:Main.cpp
+// Main.cpp
+
+#include <iostream>
+#include "sample-dll.h"
+
+int main(int argc, char **argv)
+{
+ for (int i = 0; i < 10; i++) {
+ std::cout << CountUp() << std::endl;
+ }
+}
+```
+
+今回はテストなので,sample-dll.h, sample-dll.lib, sample-dll.dll を全て Main.cpp と同じフォルダに置くことにする.
+
+追加のライブラリとして sample-dll.lib を指定するのを忘れないように.
+
+実行結果は当たり前であるが下図のようになる.
+![2015-08-09_11h45_00.png](https://qiita-image-store.s3.amazonaws.com/0/53034/6769568f-a987-1a76-53ab-1c1977fd6a6b.png)
+
+# Unity からDLLの関数を呼び出す
+
+ようやく本題である.まず適当な Unity のプロジェクトを作成し,定石通り,File > Save Scene で Scene に名前を付けて保存する.
+
+64ビットプラグインは,unity のプロジェクトが生成されたフォルダの `Assets/Plugins/x86_64` に置く.エクスプローラで sample-dll.dll をコピーする.
+
+Project の中に現れた `Assets/Plugins/x86_64/sample-dll` をクリックすると,inspector に各種情報が表示されるので,以下のように設定する(追記: 本設定は行わなくても動作する模様).
+
+一番左のタブ:
+
+- CPU: X86\_64
+- OS: Windows
+
+次のタブ:
+
+- x86\_x64 にチェック
+
+以上で使用準備は終了である.
+
+DLL呼び出しで得られた結果を Unity で表示するために,適当な物体の位置に反映させることにする.
+
+1. GameObject > 3D Object > Cube で立方体を生成する.
+
+2. Inspector の Add Component ボタンから New Script を選択し,
+ - Name: CubeBehavior
+ - Language: C Sharp
+
+ として,Create and Add をクリックする.
+
+3.CubeBehaviour を編集する.
+
+```csharp:CubeBehaviour.cs
+using UnityEngine;
+using System.Collections;
+using System.Runtime.InteropServices;
+
+public class CubeBehavior : MonoBehaviour {
+ [DllImport ("sample-dll")] private static extern int CountUp();
+ // Use this for initialization
+ void Start () {
+ }
+ // Update is called once per frame
+ void Update () {
+ transform.position = new Vector3 (0, CountUp () % 10, 0);
+ }
+}
+```
+
+実行すると,立方体が上昇→原点に戻る,という運動を繰り返すはずである.
+
+## 注意<a id="sec-4-1" name="sec-4-1"></a>
+
+本サンプルの場合,DLL内で使用されているグローバル変数 counter は,Unity の実行のたびに初期値には戻らない.実運用の場合には,設定を初期化するための関数を準備し,`Start()` 内から呼び出す必要がある.