表題とおりですが、UnityでWindows向けのNative Pluginをデバッグする方法を解説します。
と言っても解説するほどのものでもないです。
基本MSが解説しているココのとおりです。
Native Pluginとは
Unityは各プラットフォーム専用の処理をライブラリファイルとして事前にビルドし、それを利用する事が出来ます。
Windowsの場合はVisualStudioを使ってDLLファイルを作るのが一番ポピュラーだと思います。
Native PluginではUnityではできない各プラットフォーム独自の処理を色々できます。
が、もちろん別プラットフォームで使えないので移植性が大幅に下がります。
とりあえずUnityのテスト用プロジェクト用意
適当にプロジェクトを用意しました。
用意するのは、プラグインを配置する「Plugins\x86_64」フォルダと確認用スクリプトのみです。
※32bit版Unityエディターの場合は「Plugins\x86」に
用意したスクリプト
using UnityEngine; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; /// <summary> /// プラグインのテスト /// </summary> public class PluginTest : MonoBehaviour { /// <summary> /// プラグインの方 /// </summary> /// <param name="values"></param> /// <param name="length"></param> /// <returns></returns> [DllImport( "PluginTest", CharSet = CharSet.Ansi )] private static extern int plugin_dofunction( int[] values, int length ); /// <summary> /// 計算結果 /// </summary> private int result_; /// <summary> /// パラメータ /// </summary> [SerializeField] private List<int> values_; /// <summary> /// テスト /// </summary> void OnGUI() { if ( GUILayout.Button( "Plugin 関数呼び出し!" ) ) { result_ = plugin_dofunction( values_.ToArray(), values_.Count ); } var p = string.Join( ",", (from x in values_ select x.ToString()).ToArray() ); GUILayout.Label( "パラメータ:" + p ); GUILayout.Label( "結果:" + result_ ); } }
※デバッグできるのはUnityエディターが32bit版の場合は32bitのみ、64bit版の場合は64bitのみです。
両方デバッグしたいときは両方のバージョンのUnityをインストールする必要があります。
これはBuild settingsで設定したbit数とは関係ありません。
Native Plugin用意
適当にNative Plugin用意します。
プロジェクト作成
通常どおりVisual Sutdioの新規プロジェクトからWin32 Consoleを選択しDLLを選択しプロジェクトを作成します。
次に64bit版が必要なので、64bit版の設定を作成します。
※32bit版を作る場合はいりません
プロジェクト設定の上部から構成マネージャを選択します。
次に新規構成を作ります。
ここのARMの部分にx64の選択肢があるので、それを選択します。(※私はすでにx64作っているのでもうありません…)
これで64bit向けにビルドできるようになります。
ビルドイベント
次にビルド後イベントを設定します。
今回のテストでは以下のようなフォルダ構成だったので、これに合わせてビルドしたdllをコピーするスクリプトを設定しました。
PluginTest ├ unity │ └ PluginTest (Unityのプロジェクト │ └ Assets │ └ Plugins │ └ x86_64 └ dll └ PluginTest (Visual Studioのソリューション) :
スクリプトはcopyするだけ
copy $(TargetPath) $(SolutionDir)..\..\unity\PluginTest\Assets\Plugins\x86_64
プラットフォームが目当てのものか確認忘れないように。
copyコマンドも32bitの場合はx86_64ではなくx86にコピーするようにしておきましょう。
関数用意
Native Pluginの関数を用意しました。
// PluginTest.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" /** * プラグインの処理 */ extern "C" _declspec(dllexport) int plugin_dofunction(int* values, int length) { int result = 0; for (int i = 0; i < length; ++i){ result += values[i] * 2; } return result; }
受け取った配列を合算するだけのものです。
これをビルドしてUnityで確認します。
Unityで実行
Pluginsフォルダに正しく配置されていることを確認して実行します。
ボタンを押したらプラグインの関数呼びます(合算するだけのやつ)
ちゃんと呼ばれています。
では、これをデバッグします。
デバッガを使う
簡単です。Unityエディターを実行したままプラグインのプロジェクトで、「デバッグ」⇒「アタッチプロセス」を選択します。
プロセスの一覧が出ますのでその中からUnity Editorを探します。
アタッチしたら普通にブレークポイントを張ることでブレークが出来ます。
ブレークして、ウォッチで変数の中身を確認している図です。
簡単です。
割と重たいのがネックです。
注意点
注意点があります。
これはUnityでdllファイルをInspectorで見たときに注意書きで書かれているのですが、一度Unityで実行するとDLLファイルが変更できません。
そのため再度ビルドしなおしてもcopyに失敗してしまいます。こればっかりはUnityを再起動するしかありません……
上記理由で一度実行 ⇒ 変更 ⇒ 再度デバッグをしようとすると、ブレークポイントが無効(赤枠の透明丸)となって止まりません。
一度Unityを再起動してDLLをコピーしなおしてください。
Macについて
残念ながら私がMacを持っていないのでわかりません。
ただ、xcodeにはプロセスへのアタッチがあったはずなので、ライブラリを作ってVisualStudioと同様にアタッチすればデバッグできそうな気はします。
番外編: Native PluginからUnityエディターのConsoleにログを出す
ちょっとストレートなやり方が見つからなかったので、Unity側が関数ポインタを渡すという方法で対応してます。
まず、プラグイン側で関数ポインタを受け取る関数とログを表示する関数を用意します。
static void(*debugLog)(const char*) = NULL; /** * ログの表示 */ static void DebugLog(const char* text) { if (debugLog){ debugLog(text); } } /** * Debug.Logの設定 */ extern "C" _declspec(dllexport) void plugin_setDebugLog(void(*func)(const char*)) { debugLog = func; }
簡単です。
funcにUnity側の関数ポインタが来ます。
ではUnity側で設定します。
[DllImport( "PluginTest", CharSet = CharSet.Ansi )] private static extern void plugin_setDebugLog( System.IntPtr logFunction ); void Awake() { System.Action<string> logFunction = ( text ) => Debug.Log( text ); plugin_setDebugLog( Marshal.GetFunctionPointerForDelegate( logFunction ) ); }
コチラも簡単です。
Debug.Logするだけの関数をAction<string>に代入し、GetFunctionPointerForDelegateで関数ポインタを取得します。
それをネイティブ側に渡すだけ。
発展形
よくあるファイル名と行を受け取る感じにしても良いと思います。
Native側
static void(*debugLog)(const char*, const char*, int) = NULL; #define DebugLog(text) DebugLogImpl(text, __FILE__, __LINE__) /** * ログの表示 */ static void DebugLogImpl(const char* text, const char* file, int line) { if (debugLog){ debugLog(text, file, line); } } /** * Debug.Logの設定 */ extern "C" _declspec(dllexport) void plugin_setDebugLog(void(*func)(const char*, const char*, int)) { debugLog = func; }
Unity側
[DllImport( "PluginTest", CharSet = CharSet.Ansi )] private static extern void plugin_setDebugLog( System.IntPtr logFunction ); void Awake() { System.Action<string, string, int> logFunction = ( text, file, line ) => Debug.Log( string.Format( "{0}({1}) : {2}", file, line, text ) ); plugin_setDebugLog( Marshal.GetFunctionPointerForDelegate( logFunction ) ); }
これで「ファイル名.cpp(行数) : メッセージ」みたいな感じでConsoleに表示されます。