Squirrel その7

前回はC/C++プログラムからSquirrelファイルを実行する方法をやりました。
しかし、この方法ではSquirrelの標準機能を使うくらいのことしかできません。
実際にC/C++プログラム中でSquirrelを使用する場合、必要になるのは以下の機能になるでしょう。

C/C++プログラムからSquirrelの関数等を呼び出す
SquirrelからC/C++プログラムの関数等を呼び出す

どちらも極めて重要な代物なのですが、今回は前者の方を説明します。
しかし、それを説明する前に、Squirrelのスタックについて簡単に説明します。
LuaSquirrelもサブルーチンからの戻り位置や変数等は全てスタックによって管理されます。
C/C++プログラム←→Squirrelの各種機能を実装するにはSquirrelのスタックの仕組みを理解する必要があります。
基本的に、変数や引数、関数のアドレスや戻り位置などそれぞれ1つにつき1つのスタックを使用します。
例えば変数を2つ定義すると2つのスタックが使用されることになります。
スタックのインデックスには positive index と negative index の2つがあり、前者は正の整数、後者は負の整数で表されます。
positive index は1?で、ベーススタックが1になります。negative index は逆に、-1?で-1がトップスタックになります。
4つのスタックを使用しているとすると、一番最初にプッシュされたスタックが positive index では+1、negative index では -4 になります。
逆に最後にプッシュされたスタックは positive index では +4 で、negative index では -1 になります。
これを踏まえて、C/C++プログラムからSquirrel関数を呼び出してみます。

まず、適当な名前の関数を Squirrel ファイル内で定義します。
今回は foo() という名前にしてあります。引数は3つ、戻り値が存在します。
関数の処理は引数の数値を表示する程度のものでいいでしょう。
Squirrelの方はそれでOKっぽいのでC/C++プログラムの方を見ていきましょう。

sq_pushroottable(v);
sq_pushstring(v, _SC("foo"), -1);

まず何はなくともルートテーブルを設定します。
基本的に、何か新しいことをする場合はとりあえずルートテーブルを設定しておきましょう。
次にSquirrelの関数名をスタックにプッシュします。第3引数は第2引数の文字列の長さを自動計算させるための設定です。

 if(SQ_SUCCEEDED(sq_get(v, -2))){
}

次に関数のポインタ(?)を取得します。
sq_get() は関数に限らず、現在スタックトップに設定されている文字列をポップし、その文字列に対応する各種のデータ等を取得し、そのアドレス(?)等をトップスタックにプッシュする命令です。
第2引数はその文字列のデータをどのスタックに設定されているものから取得するかという意味です。
この場合、-2とは "foo" での前にプッシュされたもの、つまりルートテーブルから取得する命令です。
通常、Squirrel のグローバル関数はルートテーブルのメンバとして設定されているのでこれが正しいのです。

sq_pushroottable(v);
sq_pushinteger(v, n); 
sq_pushfloat(v, f);
sq_pushstring(v, s, -1);
sq_call(v, 4, SQTrue, 0);

関数の取得に成功すると上記のプログラムが走ります。これは、引数を設定し、その上で関数を呼び出します。
まず最初にまたもやルートテーブルを設定します。これは、いかなる関数においても暗黙的な第1引数としてルートテーブルが指定されているからです。
次の3行で整数、浮動小数点数、文字列を設定しています。引数はこれでOKです。
最後の sq_call() は第2引数の数だけスタックを引数としてポップし、その状態でトップスタックに設定されている関数を実行します。
設定された引数と第2引数の数が合わないと上手くいかないでしょう。
この段階で Squirrel に記述された foo() 関数が実行されます。
実行が終了するとプログラムは次に進みます。戻り値を取得する必要があります。

if(sq_gettype(v, -1) == OT_INTEGER){
    int  ret;
    sq_getinteger(v, -1, &ret);
    sq_pop(v, 1);
    printf("ret = %d\n", ret);
}

今回の戻り値は整数型として与えられるものという仮定で進めます。なので、最初に戻り値のタイプをチェックします。
次に sq_getinteger() で、トップスタックの数値を取得しますが、この命令では元のデータをポップすることはありませんので、取得後は sq_pop() でポップしています。
これでSquirrelの関数呼び出しは完了ですが、最後に関数を実行する前のスタック状態に戻してやる必要があります。
関数呼び出しの各種処理が行われる前に sq_gettop() 命令でそのときのスタック数を保存しておき、関数呼び出しが終わったところで保存しておいたスタック数を sq_settop() にて設定すればOKです。
もしもどこかにポップされていない命令等があったとしてもこうすれば元の状態に戻すことができます。
スタックの状態さえ理解できていれば難しいことは何もないと思います。

というわけで今回は終了。
次回は逆に Squirrel からC/C++プログラム内の関数を呼び出す方法をやります。