Squirrel その8

ちょっとだけワールドカップの話。
ドイツ×スウェーデン戦は前半10分までが面白かった。主にクローゼ。
それ以降はクローゼにあんまりボールが回らないし、バラックはミドルばっかり狙うし。
特にドイツの守備陣は酷すぎる。スウェーデンの攻撃陣がいつもの調子だったら2点くらい取られててもおかしくない。
次のアルゼンチン戦はヤバイだろう。
イングランド×エクアドル戦はベッカムのFK以外は糞みたいに面白くない試合だった。
イタリア×オーストラリアは途中で寝た。上記の試合より良かったけど、それでもあんまり面白くなかったし。
ブラジル×ガーナには期待しよう。

さて、今回はC/C++の関数をSquirrelから呼び出す方法についてやります。
まず登録する関数を作成しますが、この関数は SQFUNCTION 型の関数である必要があります。
この関数は HSQUIRRELVM を引数とし、SQInteger を返す関数です。
今回はその関数としてSquirrelのヘルプにもある関数を使用します。

SQInteger print_args(HSQUIRRELVM v)
{
    SQInteger nargs = sq_gettop(v);
    for(SQInteger n=1; n<=nargs; n++){
        printf("arg %d is ", n);
        switch(sq_gettype(v, n)){
            case OT_NULL:
                printf("null\n");        
                break;
(中略)
            default:
                return sq_throwerror(v, "invalid param");
        }
    }
    printf("\n");
    sq_pushinteger(v, nargs);
    return 1;
}

この関数は渡された引数全ての型を表示するというものです。(中略)部分はヘルプを参照してください。
引数の数を調べるにはスタックトップの正のインデックスを取得すればそれがそのまま引数の数になります。
sq_gettop() 関数はVMのスタックトップの正のインデックスを取得する命令ですが、これは HSQUIRRELVM のメンバ _top を持ってくるのではなく、_stackbase と _top の差を持ってくる関数です。
つまり、SquirrelからC/C++関数が呼ばれると呼び出し位置のスタックトップがスタックベースとなり、そこから引数をスタックに突っ込んでいくことになります。
スタックに入っている値の型を調べる sq_gettype() 関数にはインデックスとして 1? の数値を与えます。
1がスタックベースです。0 を指定した場合、動作の保証はできません(必ずハングするというわけではない)。
この関数をSquirrelから呼び出すと、引数の数は指定した引数の数より1つだけ多くなるはずです。
前回も説明した通り、関数を呼び出すと暗黙的にルートテーブルが最初の引数として指定されます。
なので、この関数を呼び出すと、必ず最初の引数の型はテーブルになるはずです。
では、この関数を登録する方法を以下に示します。

void register_global_func(HSQUIRRELVM v, SQFUNCTION f, const char* fname)
{
    sq_pushroottable(v);
    sq_pushstring(v, fname, -1);
    sq_newclosure(v, f, 0);
    sq_createslot(v, -3);
    sq_pop(v, 1);
}

この関数はやはりヘルプに載っているものですが、関数を登録するだけならこの関数を使っておけば問題ありません。
まず最初にルートテーブルをプッシュします。これはほとんどお約束。
次にSquirrelから呼び出す際の関数のIDを設定します。第3引数は-1がお約束(文字列の長さを自動的に計算するため)。
次に関数を登録します。sq_newclosure() 関数で登録できますが、第3引数はよくわかりません。
とりあえず 0 で問題ないっぽいですが。
次の sq_createslot() 関数はスタックトップとその下のスタックを何らかの値とそのIDと仮定し、第3引数で指定されているスタック位置の値に新しいスロットとして作成する関数です。
つまり、新しいクロージャ f をID fname としてスタックインデックス -3、つまりルートテーブルのスロットとして作成するわけです。
最後にルートテーブルをポップします。ポップしてもルートテーブルが初期化されるわけではないので問題ありません。
関数の登録はVMの作成後、Squirrelファイルの実行前に行います。

v = sq_open(1024);
sq_pushroottable(v);
register_global_func(v, print_args, "prt");
if(SQ_SUCCEEDED(sqstd_dofile(v, _SC("test.nut"), 0, 1))){
}

この例では print_args() 関数はSquirrelファイル内で prt というIDを使って呼び出されることになります。
今回はこれにて終了。次回はもう少し高度なことをやっていきましょう。