凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

ネイティブプラグインから Unity の関数(Debug.Log 等)を呼び出す

はじめに

ネイティブプラグインの作成時にネイティブ側から Unity の Debug.Log() を呼んでデバッグしたくなるときが多々あります。以下のエントリ(英語)で方法が紹介されていたのでやってみました。

ざっくり3行

  1. C# 側で適当な関数を作成
  2. その関数を呼び出す unmanaged なデリゲートを作成
  3. デリゲートの関数ポインタC++ の関数ポインタに代入

環境

具体的なコード

C++

こんな関数を C# 側へ晒します。

#if defined(_WIN32) || defined(_WIN64)
    #define UNITY_INTERFACE_EXPORT __declspec(dllexport)
#else
    #define UNITY_INTERFACE_EXPORT
#endif


extern "C"
{
    using debug_log_func_type = void(*)(const char*);
    
    namespace
    {
        debug_log_func_type debug_log_func = nullptr;
    }
    
    void debug_log(const char* msg)
    {
        if (debug_log_func != nullptr) debug_log_func(msg);
    }
    
    UNITY_INTERFACE_EXPORT void set_debug_log_func(debug_log_func_type func)
    {
        debug_log_func = func;
    }
    
    UNITY_INTERFACE_EXPORT void debug_log_test()
    {
        debug_log("hogehoge");
    }
}

set_debug_log_func()C# 側から呼び出し Debug.Log() を行う関数ポインタをセットします。その後にセットされた関数を呼び出す debug_log_test()C# から呼び出し、Unity のコンソールに hogehoge が出力されれば成功です。

C#

以下の様なコードを用意します。

using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class NativeDebugLog : MonoBehaviour 
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void DebugLogDelegate(string str);
    DebugLogDelegate debugLogFunc = msg => Debug.Log(msg);
    
    [DllImport ("unity_debug_log")]
    public static extern void set_debug_log_func(IntPtr ptr);
    [DllImport ("unity_debug_log")]
    public static extern void debug_log_test();

    void Start() 
    {
        var callback = new DebugLogDelegate(debugLogFunc);
        var ptr = Marshal.GetFunctionPointerForDelegate(callback);
        set_debug_log_func(ptr);
        debug_log_test();
    }
}

UnmanagedFunctionPointerAttributeMarshal.GetFunctionPointerForDelegate() を使って関数ポインタを取り出してセットしています。

結果

実行すると無事 hogehoge と出力されます。

f:id:hecomi:20160103170600p:plain

もっと簡単な方法

以下のエントリを読むとそのままデリゲートを渡しても問題ないようです。

なので C# 側のコードを以下のように書き換えても動きます。

using UnityEngine;
using System.Runtime.InteropServices;

public class NativeDebugLog : MonoBehaviour 
{
    public delegate void DebugLogDelegate(string str);
    DebugLogDelegate debugLogFunc = msg => Debug.Log(msg);

    [DllImport ("unity_debug_log")]
    public static extern void set_debug_log_func(DebugLogDelegate func);
    [DllImport ("unity_debug_log")]
    public static extern void debug_log_test();

    void Start() 
    {
        set_debug_log_func(debugLogFunc);
        debug_log_test();
    }
}

おわりに

デバッグにも便利ですし、他にも色々と使えそうです。