プログラミング/組み込み言語/Sqrat/概要と導入 の変更点


Sqratの概要、導入手順、および基本的な使い方について説明しています。~
Sqratの各クラスの説明については[[別ページを参照>../各クラスの説明]]してください。

#contents


*概要 [#about]

**Sqratとは [#about-sqrat]

''Sqrat''とは、Brandon Jones氏によって開発されている、組み込み言語Squirrelをより便利に扱うためのC++用ライブラリです。~
Squirrelについては[[主な言語まとめ>../../主な言語まとめ]]のページの[[Squirrelの項>../../主な言語まとめ#squirrel]]を参照してください。

-[[Sqrat - SourceForge.net>http://sourceforge.net/projects/scrat/]]

Sqratライブラリは次の3つの機能を提供しています。

-バインダ
-モジュールインポート
-スレッドモジュール

C++とSquirrelのバインダとしてはSqPlusが有名です。~
両者の違いは、SqPlusがVM(仮想マシン)を含む全てをラップしているのに対して、SqratではVMに対する一連の操作(C++クラスのバインド等)に絞ってラップしていることです。~
そのため、SqPlusを使う場合はすべてSqPlusで書かなければなりませんが&note{:そういう例しか見たことがないのでそうだと思っているのですが、間違っているかも…。};、Sqratを使う場合は必要に応じてSquirrelのAPIを直に使うこともできます。

**この文書の位置付け [#about-page]

この文書では、Sqratの機能の一つである''バインダ''の導入手順および簡単な内容の説明を行います。~
モジュールインポートやスレッドモジュールについては一切触れません。

Sqratにはわかりやすいドキュメントが付属しており、普通はそれを見るだけで利用の上では問題ありません。~
しかし、ドキュメントでは触れられておらず、コードを見て初めてわかる機能もいくつかあります。~
また、ドキュメントは英語であるため、英語恐怖症を患っている方には辛いものがあります(サンプルコードが多いので英語が読めなくても大体理解できるのですが)。

そこで、''ドキュメントに載っている情報+α''を''日本語''で説明するのがこの文書の主旨となります。~
ただし、筆者の理解が完璧であるとは言えないため、間違いを含む可能性があることに留意してください。


*導入手順 [#install]

**Squirrelの導入 [#install-squirrel]

Sqratを使うためには、当然ながら使用するプログラムでSquirrelを扱えるようにする(即ち、Squirrelのライブラリをリンクする)必要があります。~
SqratがサポートするのはSquirrel2.2系なので、まずは[[Squirrelのダウンロードページ>http://sourceforge.net/projects/squirrel/files/]]から2.2系の最新版を落としてきましょう。

圧縮ファイルを展開後、 make できる環境(Linux等)であれば普通にトップディレクトリで make するだけです。~
lib ディレクトリ内に libsquirrel.a および libsqstdlib.a ができあがります。

WindowsでVisual Studio 2005やVisual Studio 2008を使って開発している場合、次の手順でビルドします。

+展開したディレクトリ内の squirrel.dsw を開きます。
+プロジェクト形式を変換するか確認されるので、すべて変換します。
+3つのプロジェクトの設定を必要に応じて変更します。~
どのように変更しても構いませんが、少なくともsquirrelプロジェクトとsqstdlibプロジェクトは同じ設定にしておきましょう。
--マルチバイト文字セットではなくUnicode文字セットを使いたい場合は構成プロパティの全般→文字セットの項を変更。
--VC++ランタイムライブラリを静的リンクではなく動的リンクさせたい場合はC/C++→コード生成→ランタイムライブラリの項を変更。
+マルチバイト文字設定で使う場合、[[後述する内容>#install-squirrel-sjis]]に従ってソースコードを修正します。
+DebugもしくはReleaseの構成でソリューションのビルドを行います。~
C4996の警告が大量に出ますが、実害は無いので無視して構いません。
+lib ディレクトリ内に squirrel.lib および sqstdlib.lib ができあがります。

プログラムに組み込むには、 include ディレクトリおよび lib ディレクトリをそれぞれインクルードパス、ライブラリパスに追加し、squirrelライブラリおよびsqstdlibライブラリをプログラムにリンクすればOKです。

***Windowsで日本語(Shift_JIS)を扱いたい場合 [#install-squirrel-sjis]

Windowsかつ非Unicode設定でSquirrelを使用する場合、そのままでは日本語(Shift_JIS)を扱うことができません。~
squirrelプロジェクトのソースファイル sqlexer.cpp の330行目付近(@code{default}; 句の直後)に次のようにコードを追加する必要があります。~
@code{#if}; と @code{#endif}; で囲まれている部分が追加する行となります。

#code(c,nonumber){{
                    case _SC('"'): APPEND_CHAR(_SC('"')); NEXT(); break;
                    case _SC('\''): APPEND_CHAR(_SC('\'')); NEXT(); break;
                    default:
                        Error(_SC("unrecognised escaper char"));
                    break;
                    }
                }
                break;
            default:
#if defined(WIN32) && !defined(SQUNICODE)
                if (
                    ((CUR_CHAR >= 0x81) && (CUR_CHAR <= 0x9F)) ||
                    ((CUR_CHAR >= 0xE0) && (CUR_CHAR <= 0xFF))
                ) {
                    APPEND_CHAR(CUR_CHAR);
                    NEXT();
                    if (CUR_CHAR == ndelim) { break; }
                }
#endif
                APPEND_CHAR(CUR_CHAR);
                NEXT();
            }
        }
}}

取得した文字コードがShift_JISの2バイト文字の上位1バイトであった場合、その次の文字コードまで読み取るという処理です。~
このコードは次のページを参考にして記述しました。

-[[もんしょの巣穴blog Squirrel おまけ>http://monsho.blog63.fc2.com/blog-entry-49.html]]

**Sqratのバインダの導入 [#install-sqrat]

Squirrelの導入が済んでいれば、Sqratのバインダの導入自体はとても簡単です。

+[[Sqratのダウンロードページ>http://sourceforge.net/projects/scrat/]]から最新版を落としてきます。
+圧縮ファイルを展開した中にある include ディレクトリをインクルードパスに追加します。
+バインダを使用したいソースコードに @code{#include <sqrat.h>}; を書きます。

これだけです。~
Sqratのバインダはテンプレートクラス群として提供されており、予めビルドすべきものはありません。


*基本的な使い方 [#basic]

この項ではSqratの基本的な使い方を説明します。~
Sqratで定義されているクラスの詳細については[[各クラスの説明>../各クラスの説明]]のページを参照してください。

**初期化と解放 [#basic-ctor-dtor]

SqratはSquirrelのVM自体をラップしてはいません。~
そのため、VMのインスタンスは普通にSquirrelのAPIを使うときと同じようにして初期化、解放する必要があります。~
大抵は次のように書くことになるでしょう。

#code(c){{
#include <sqrat.h>      // squirrel.h もインクルードされる
#include <sqstdaux.h>   // sqstd_seterrorhandlers

#include <stdio.h>
#include <stdarg.h>

//! 文字列出力関数
static void printfunc(HSQUIRRELVM vm, const SQChar* format, ...)
{
    va_list args;
    va_start(args, format);
#ifdef SQUNICODE
    vwprintf(format, args);
#else
    vprintf(format, args);
#endif
    va_end(args);
}

//! メイン関数
int main(int argc, char** argv)
{
    // Sqratの名前空間
    using namespace Sqrat;

    // VMを作成
    HSQUIRRELVM vm = sq_open(1024);

    // 標準のエラーハンドラを設定
    sqstd_seterrorhandlers(vm);

    // 文字列出力関数を設定
    sq_setprintfunc(vm, printfunc);

    //
    // ※ここにSqrat(もしくはSquirrelのAPI)を用いたコードを書く
    //

    // VMを解放
    sq_close(vm);

    return 0;
}
}}

当文書での説明におけるC++サンプルコード片は、上記コード中の @code{※}; の部分のみ書かれている場合があります。

**C++からSquirrelへ [#basic-c2s]

***クラスやテーブルのバインド [#basic-c2s-bind]

C++のクラスや関数等をSquirrelのVMにバインドする基本的な手順は次のようになります。

+クラスやテーブル(C++でいう名前空間のようなもの)を定義するオブジェクトを作成する。
+作成したオブジェクトをルートテーブルもしくは他のテーブルにバインドする。

簡単なC++コーディング例を次に示します。~
@code{bindMyTable}; 関数は、 @code{MyClass}; クラスおよび @code{printNum}; 関数を @code{MyTable}; テーブルにバインドし、その @code{MyTable}; テーブルをルートテーブルにバインドしています。

#code(c){{
// クラス
class MyClass
{
public:
    int mul(int x, int y) { return (x * y); }
    int _bar;
};

// 関数
void printNum(int n)
{
    printf("num = %d\n", n);
}

void bindMyTable(HSQUIRRELVM vm)
{
    using namespace Sqrat;

    // テーブルを作成
    Table myTable(vm);

    // MyClass オブジェクトを作成
    Class<MyClass> myClass(vm);

    // MyClass オブジェクトにメンバをバインド
    myClass.Func(_SC("mul"), &MyClass::mul);     // メンバ関数
    myClass.Var(_SC("_bar"), &MyClass::_bar);    // メンバ変数

    // テーブルに MyClass と printNum をバインド
    myTable.Bind(_SC("MyClass"), myClass);
    myTable.Func(_SC("printNum"), &printNum);

    // ルートテーブルに myTable を "MyTable" としてバインド
    RootTable(vm).Bind(_SC("MyTable"), myTable);
}
}}

なお、コード中の @code{_SC}; はSquirrelで定義されているマクロであり、意味は @code{_T}; マクロと同じです。~
@code{_T}; マクロについては当サイトの[[Unicode対応コーディング>プログラミング/小ネタ集/Unicode対応コーディング]]のページを参照してください。

この関数を通したVMでは、次のようなSquirrelスクリプトを実行することができます。

#code(squirrel){{
instance <- MyTable.MyClass();
instance._bar = 2;
MyTable.printNum(instance.mul(instance._bar, 7)); // "num = 14\n"
}}

ちなみに、テーブルへの @code{MyClass}; クラスのバインドは次のようにまとめて書くこともできます。~
どちらでも好きな書き方で書くとよいでしょう。

#code(c){{
void bindMyTable(HSQUIRRELVM vm)
{
    using namespace Sqrat;

    // テーブルを作成
    Table myTable(vm);

    // テーブルに MyClass をバインド
    myTable.Bind(
        _SC("MyClass"),
        Class<MyClass>(vm)
            .Func(_SC("mul"), &MyClass::mul)    // メンバ関数
            .Var(_SC("_bar"), &MyClass::_bar)   // メンバ変数
        );

    // テーブルに printNum をバインド
    myTable.Func(_SC("printNum"), &printNum);

    // ルートテーブルに myTable を "MyTable" としてバインド
    RootTable(vm).Bind(_SC("MyTable"), myTable);
}
}}

なお、この項のコーディング例のようにしてクラスオブジェクトを作成する場合、そのクラスで引数無しのコンストラクタが公開されている必要があります。~
コンストラクタを公開せずにクラスオブジェクトを作成する方法については、[[各クラスの説明の Class<C,A> クラスの項>../各クラスの説明#class]]を参照してください。

***変数への値のバインド [#basic-c2s-setvar]

C++の値やインスタンスをSquirrelのVMにグローバル変数としてバインドするコーディング例を次に示します。~
なお、あらかじめ @code{MyStruct}; 構造体と @code{MyClass}; クラスの定義をバインド済みであるものとします。

#code(c){{
int num = 100;                      // 数値
Sqrat::string text = _SC("test");   // 文字列値
MyStruct msData;                    // MyStructインスタンス
MyClass mcInst;                     // MyClassインスタンス

// 値の設定
RootTable(vm).BindValue(_SC("num"), num);
RootTable(vm).BindValue(_SC("text"), text);
RootTable(vm).BindValue(_SC("msData"), msData);

// インスタンスの設定
RootTable(vm).BindInstance(_SC("mcInst"), &mcInst);
}}

値のバインドには @code{BindValue}; メンバ関数を、インスタンスのバインドには @code{BindInstance}; メンバ関数を用います。~
どちらも第一引数が変数名、第二引数が値となります。~
対象テーブル内に指定した名前の変数が存在しない場合は新規作成され、存在する場合は上書きされます。

@code{BindValue}; メンバ関数でバインドした値はSquirrel側で操作してもC++側には反映されません(コピーが渡される)。~
一方、 @code{BindInstance}; メンバ関数でバインドしたインスタンスをSquirrel側で操作するとC++側にも反映されます(参照が渡される)。

上記のコーディング例ではルートテーブルにバインドしてグローバル変数としていますが、ルートテーブル以外のテーブルにも同様にしてバインドできます。

**SquirrelからC++へ [#basic-s2c]

***変数の値の取得 [#basic-s2c-getvar]

次のようなSquirrelスクリプトを実行済みのVMがあるとします。

#code(squirrel){{
valInt <- 100;
valFloat <- 2.5;
valStr <- "Hello, Sqrat World !!";

valTable <- { valT = 50 }

class FooClass
{
    constructor(v)
    {
        valC = v;
    }

    valC = null;
}

valClass <- FooClass("test class");
}}

この時、グローバル変数やクラスのメンバ変数などの値は次のコーディング例のようにして取得できます。

#code(c){{
// valInt <- 100;
Object objValInt = RootTable(vm).GetSlot(_SC("valInt"));
int valInt = objValInt.Cast<int>();

// valFloat <- 2.5;
Object objValFloat = RootTable(vm).GetSlot(_SC("valFloat"));
float valFloat = objValFloat.Cast<float>();

// valStr <- "Hello, Sqrat World !!";
Object objValStr = RootTable(vm).GetSlot(_SC("valStr"));
Sqrat::string valStr = objValStr.Cast<Sqrat::string>();

// valTable <- { valT = 50 }
Object objValTable = RootTable(vm).GetSlot(_SC("valTable"));
Object objValT = objValTable.GetSlot(_SC("valT"));
int valT = objValT.Cast<int>();

// valClass <- FooClass("test class");
Object objValClass = RootTable(vm).GetSlot(_SC("valClass"));
Object objValC = objValClass.GetSlot(_SC("valC"));
Sqrat::string valC = objValC.Cast<Sqrat::string>();
}}

@code{GetSlot}; メンバ関数によってSquirrel変数の値を @code{Object}; クラスのインスタンスとして取得できます。~
そして @code{Cast<T>}; メンバ関数によって目的の型として取得できます。

変数が存在するかどうかわからない場合は、次のコーディング例のように @code{IsNull}; メンバ関数を用いてチェックします。~
@code{IsNull}; メンバ関数は変数を取得できなかった場合(即ち @code{null}; が返った場合)に真となります。

#code(c){{
int valInt = -1;
Object objValInt = RootTable(vm).GetSlot(_SC("valInt"));
if (!objValInt.IsNull())
{
    // Squirrel変数 valInt が存在する場合はここを通る
    valInt = objValInt.Cast<int>();
}
}}

***関数の取得と実行 [#basic-s2c-getfunc]

Squirrelでは関数も値の一つです。~
しかしSqratでは、Squirrelの関数をC/C++の関数的に扱える仕組みが用意されています。

次のようなSquirrelスクリプトを実行済みのVMがあるとします。

#code(squirrel){{
// 受け取った文字列を改行付きで出力する関数
function printLine(str)
{
    print(str + "\n");
}

// 受け取った引数の合計値を返す関数
function calcTotal(...)
{
    local ret = 0;
    for (local i = 0; i < vargc; ++i)
    {
        ret += vargv[i];
    }
    return ret;
}
}}

この時、Squirrel関数は次のようにして取得および実行できます。

#code(c){{
// 関数を取得
Function funcPrintLine = RootTable(vm).GetFunction(_SC("printLine"));
Function funcCalcTotal(RootTable(vm), _SC("calcTotal"));

// 関数を実行する(結果不要な場合)
funcPrintLine.Execute(_SC("てすと1"));
funcPrintLine(_SC("てすと2"));

// 関数を実行して結果を得る
int resI = funcCalcTotal.Evaluate<int>(1, 2, 3, 4);             // 10
float resF = funcCalcTotal.Evaluate<float>(1.1F, 2.2F, 3.3F);   // 6.6F
}}

関数の取得にはテーブルまたはクラスの @code{GetFunction}; メンバ関数を用います。~
もしくは @code{Function}; クラスのコンストラクタでも同等のことが行えます。~
ちなみに、変数の場合と同じく、 @code{IsNull}; メンバ関数で存在チェックできます。

取得した関数の実行には @code{Execute}; メンバ関数か @code{Evaluate}; メンバ関数を用います。~
返り値が不要な場合には @code{Execute}; メンバ関数を用います(上記の例のように @code{operator()}; でも行えます)。~
返り値が必要な場合には @code{Evaluate}; メンバ関数を用い、テンプレート引数に返り値の型を指定します。

引数および返り値の型には、 @code{int}; や @code{float}; 等の基本型に加え、あらかじめバインドしておいたクラス型を用いることもできます。

*各クラスの説明 [#classes]

[[各クラスの説明>../各クラスの説明]]のページにて。