c++プラグインでSquirrelスクリプトとやり取りをする（Android）

#環境
Unity5.6.2f1
Squirrel 3.1

#概要
c++Likeなスクリプト言語のSquirrelとやり取りをするテストです

#Squirrelのダウンロード
以下のページで「Download」を押す
https://sourceforge.net/projects/squirrel/

#AndroidStudioとUnityの事前設定
以下を参照してプロジェクトを作成する
<a href=http://qiita.com/fukaken5050/items/f0ebc9977c8de05b29ba target=_blank>プラグインでc++と連携する（Android）</a>
libunisquirrel.soとしてエクスポートする

#Squirrel関連のファイル追加
・AndroidのNativePluginを作成した時の「cpp」フォルダ内に「squirrel3」というフォルダを作成
・ダウンロードしたtar.gzを展開する
・「squirrel」「sqstdlib」「include」フォルダを作成した「squirrel3」の中にフォルダ毎コピーする
・コピーしたフォルダ内の「.cpp」と「.h」以外を削除する

#CMakeLists.txt

~~~
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             UniSquirrel.cpp
             #squirrel3
             squirrel3/squirrel/sqapi.cpp
             squirrel3/squirrel/sqbaselib.cpp
             squirrel3/squirrel/sqclass.cpp
             squirrel3/squirrel/sqcompiler.cpp
             squirrel3/squirrel/sqdebug.cpp
             squirrel3/squirrel/sqfuncstate.cpp
             squirrel3/squirrel/sqlexer.cpp
             squirrel3/squirrel/sqmem.cpp
             squirrel3/squirrel/sqobject.cpp
             squirrel3/squirrel/sqstate.cpp
             squirrel3/squirrel/sqtable.cpp
             squirrel3/squirrel/sqvm.cpp
             squirrel3/sqstdlib/sqstdaux.cpp
             squirrel3/sqstdlib/sqstdblob.cpp
             squirrel3/sqstdlib/sqstdio.cpp
             squirrel3/sqstdlib/sqstdmath.cpp
             squirrel3/sqstdlib/sqstdrex.cpp
             squirrel3/sqstdlib/sqstdstream.cpp
             squirrel3/sqstdlib/sqstdstring.cpp
             squirrel3/sqstdlib/sqstdsystem.cpp

)

# Specifies a path to native header files.
include_directories( Unity/ )
include_directories( squirrel3/include/ )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log
)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
)
~~~

#プログラム
Squirrelの「.nut」ファイルはUTF8（bom無し）CR+LFでテストしました

UniSquirrel.cpp

~~~c++
#include "IUnityInterface.h"
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
//#define SQUNICODE
#include "squirrel.h"
#include "sqstdio.h"
#include "sqstdaux.h"

static void printfunc( HSQUIRRELVM v, const SQChar* pFormat, ... );
static SQInteger print_args( HSQUIRRELVM v );
static SQInteger register_global_func( HSQUIRRELVM v, SQFUNCTION f, const char *fname );
static void CallSquirrelFunc( HSQUIRRELVM v );

extern "C"
{
    using CallbackOutputString = void(*)( const SQChar* );
    namespace
    {
        CallbackOutputString onCallbackOutputString = NULL;
    }

    UNITY_INTERFACE_EXPORT void SetCallbackOutputString( CallbackOutputString func )
    {
        onCallbackOutputString = func;
    }

    UNITY_INTERFACE_EXPORT void ExecuteScript( const SQChar* pPath )
    {
        if ( onCallbackOutputString == NULL )
            return;

        HSQUIRRELVM v;
        v = sq_open( 1024 );
        sqstd_seterrorhandlers( v );
        sq_setprintfunc( v, printfunc, printfunc );
        sq_pushroottable( v );
        //c/c++関数の登録
        register_global_func( v, print_args, "print_args" );

        if( SQ_SUCCEEDED( sqstd_dofile( v, pPath, 0, 1 ) ) )
        {
            printf("Call Failed!");
        }

        //squirrel関数の呼び出し
        CallSquirrelFunc( v );

        sq_close(v);
    }

    UNITY_INTERFACE_EXPORT void ExecuteScriptInMemory( const SQChar* pBuffer )
    {
        if ( onCallbackOutputString == NULL )
            return;

        HSQUIRRELVM v;
        v = sq_open( 1024 );
        sqstd_seterrorhandlers( v );
        sq_setprintfunc( v, printfunc, printfunc );
        sq_pushroottable( v );

        sq_compilebuffer( v, pBuffer,(int)strlen( pBuffer ) * sizeof( SQChar ), "compile", 1 );
        sq_pushroottable(v);
        sq_call(v,1,1,0);

        sq_close(v);
    }

    static void CallSquirrelFunc( HSQUIRRELVM v )
    {
        //SQInteger saveStack = sq_gettop( v );
        sq_pushroottable( v );
        sq_pushstring( v, _SC( "TestFunc" ), -1 );
        if( SQ_SUCCEEDED( sq_get( v, -2 ) ) )
        {
        }
        int n = 123;
        float f = 345.67f;
        const SQChar* s = _SC( "abcd" );
        sq_pushroottable( v );
        sq_pushinteger( v, n );
        sq_pushfloat( v, f );
        sq_pushstring( v, s, -1 );
        sq_call( v, 4, SQTrue, 0 );

        if( sq_gettype( v, -1 ) == OT_INTEGER )
        {
            SQInteger ret;
            sq_getinteger( v, -1, &ret );
            sq_pop( v, 1 );
        //    sq_settop( v, saveStack );
            char str[64] = "";
            snprintf( str, sizeof( str ) - 1, "%d", ret );
            onCallbackOutputString( str );
        }
    }

    static void printfunc( HSQUIRRELVM v, const SQChar* pFormat, ... )
    {
        va_list arglist;
        va_start( arglist, pFormat );
        SQChar buffer[ 1024 ] = _SC( "" );
        vsnprintf( buffer, sizeof(buffer), pFormat, arglist );
        va_end( arglist );

        onCallbackOutputString( buffer );
    }

    static SQInteger print_args( HSQUIRRELVM v )
    {
        SQInteger nargs = sq_gettop(v); //number of arguments
        for(SQInteger n=1;n<=nargs;n++)
        {
            printf("arg %d is ",n);
            switch(sq_gettype(v,n))
            {
                case OT_NULL:
                    onCallbackOutputString("null");
                    break;
                case OT_INTEGER:
                    onCallbackOutputString("integer");
                    break;
                case OT_FLOAT:
                    onCallbackOutputString("float");
                    break;
                case OT_STRING:
                    onCallbackOutputString("string");
                    break;
                case OT_TABLE:
                    onCallbackOutputString("table");
                    break;
                case OT_ARRAY:
                    onCallbackOutputString("array");
                    break;
                case OT_USERDATA:
                    onCallbackOutputString("userdata");
                    break;
                case OT_CLOSURE:
                    onCallbackOutputString("closure(function)");
                    break;
                case OT_NATIVECLOSURE:
                    onCallbackOutputString("native closure(C function)");
                    break;
                case OT_GENERATOR:
                    onCallbackOutputString("generator");
                    break;
                case OT_USERPOINTER:
                    onCallbackOutputString("userpointer");
                    break;
                default:
                    return sq_throwerror(v,"invalid param"); //throws an exception
            }
            onCallbackOutputString("\n");
        }
        sq_pushinteger(v,nargs); //push the number of arguments as return value
        return 1; //1 because 1 value is returned
    }

    static SQInteger register_global_func( HSQUIRRELVM v, SQFUNCTION f, const SQChar* fname )
    {
        sq_pushroottable(v);
        sq_pushstring(v,fname,-1);
        sq_newclosure(v,f,0); //create a new function
        sq_createslot(v,-3);
        sq_pop(v,1); //pops the root table
        return 0;
    }
}
~~~

/Assets/TestSquirrel.cs

~~~c#
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System.Runtime.InteropServices;
using System.IO;
using System.Collections;

public class TestSquirrel : MonoBehaviour 
{
	[SerializeField] Text _text = null;

	[DllImport ("unisquirrel")]
	public static extern void SetCallbackOutputString( UnityAction<string> func );
	[DllImport ("unisquirrel")]
	public static extern void ExecuteScript( string path );
	[DllImport ("unisquirrel")]
	public static extern void ExecuteScriptInMemory( string buffer );

	private static TestSquirrel _instance = null;
	private void Awake()
	{
		_instance = this;
	}

	private void OnDestroy()
	{
		_instance = null;
	}

	private void OnApplicationQuit()
	{
		OnDestroy();
	}

	private void OnEnable()
	{
		SetCallbackOutputString( OnOutputString );
	}

	private void OnDisable()
	{
		SetCallbackOutputString( null );
	}

	private IEnumerator Start() 
	{
		yield return StartCoroutine( ExecuteScriptProcess( "test1.nut" ) );
		yield return StartCoroutine( ExecuteScriptInMemoryProcess( "test0.nut" ) );
	}

	private IEnumerator ExecuteScriptProcess( string fileName )
	{
		var filePath = Path.Combine(Application.streamingAssetsPath, fileName );
		var scriptData = "";

		if ( filePath.Contains( "://" ) == true )
		{
			WWW www = new WWW( filePath );
			yield return www;
			scriptData = www.text;
		}
		else
			scriptData = System.IO.File.ReadAllText( filePath );

		//persistentDataPathへコピー
		var outputPath = Path.Combine( Application.persistentDataPath, fileName );
		File.WriteAllText( outputPath, scriptData );
		while( File.Exists( outputPath ) == false )
			yield return null;

		Debug.Log( scriptData );
		ExecuteScript( outputPath );
		yield break;
	}

	private IEnumerator ExecuteScriptInMemoryProcess( string fileName )
	{
		var filePath = Path.Combine( Application.streamingAssetsPath, fileName );
		var scriptData = "";

		if ( filePath.Contains( "://" ) == true )
		{
			WWW www = new WWW( filePath );
			yield return www;
			scriptData = www.text;
		}
		else
			scriptData = File.ReadAllText( filePath );

		Debug.Log( scriptData );
		ExecuteScriptInMemory( scriptData );
		yield break;
	}

	private void OnOutputString( string message )
	{
		Debug.Log( message );
		_instance.SetText( message );
	}

	private void SetText( string message )
	{
		if( _text == null )
			return;
		_text.text = message;
	}
}
~~~

Assets/StreamingAssets/test0.nut

~~~c++
print("ハローワールド");
~~~

Assets/StreamingAssets/test1.nut

~~~c++
function TestFunc( n, f, s )
{
	print( "TestFunc:" + n + "/" + f + "/" + s );
	return 789;
}

function Main()
{
	print( "ハロー！ワールド" );
	print_args( 987, 654.32, "zxcv" );
}
Main();
~~~