Home / プログラミング / 組み込み言語 / Sqrat / 概要と導入
概要と導入

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

概要

Sqratとは

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

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

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

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

この文書の位置付け

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

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

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

導入手順

Squirrelの導入

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

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

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

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

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

Windowsで日本語(Shift_JIS)を扱いたい場合

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

すべて開くすべて閉じる
 
 
 
 
 
!
!
|
|
|
|
|
|
-
|
|
|
!
|
|
|
!
!
                    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バイトであった場合、その次の文字コードまで読み取るという処理です。
このコードは次のページを参考にして記述しました。

Sqratのバインダの導入

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

  1. Sqratのダウンロードページから最新版を落としてきます。
  2. 圧縮ファイルを展開した中にある include ディレクトリをインクルードパスに追加します。
  3. バインダを使用したいソースコードに #include <sqrat.h> を書きます。

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

基本的な使い方

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

初期化と解放

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 
 
 
 
 
-
!
-
|
|
|
|
|
|
|
|
!
 
-
!
-
-
!
|
-
!
|
-
!
|
-
!
|
-
|
|
!
-
!
|
|
!
#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++サンプルコード片は、上記コード中の の部分のみ書かれている場合があります。

C++からSquirrelへ

クラスやテーブルのバインド

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

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
-
!
-
|
|
|
!
 
-
!
-
|
!
 
 
-
|
|
-
!
|
-
!
|
-
!
|
|
-
!
|
|
-
!
!
// クラス
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);
}

なお、コード中の _SC はSquirrelで定義されているマクロであり、意味は _T マクロと同じです。
_T マクロについては当サイトのUnicode対応コーディングのページを参照してください。

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

すべて開くすべて閉じる
  1
  2
  3
 
 
 
instance <- MyTable.MyClass();
instance._bar = 2;
MyTable.printNum(instance.mul(instance._bar, 7)); // "num = 14\n"

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
-
|
|
-
!
|
-
!
|
|
|
|
|
|
-
!
|
-
!
!
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> クラスの項を参照してください。

変数への値のバインド

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
 
 
 
-
!
 
 
 
-
!
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);

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

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

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

SquirrelからC++へ

変数の値の取得

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 
 
 
 
 
 
 
-
|
-
|
!
|
|
!
 
 
valInt <- 100;
valFloat <- 2.5;
valStr <- "Hello, Sqrat World !!";
 
valTable <- { valT = 50 }
 
class FooClass
{
    constructor(v)
    {
        valC = v;
    }
 
    valC = null;
}
 
valClass <- FooClass("test class");

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
-
!
 
 
-
!
 
 
-
!
 
 
-
!
 
 
 
-
!
 
 
// 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>();

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
 
 
 
-
-
!
!
int valInt = -1;
Object objValInt = RootTable(vm).GetSlot(_SC("valInt"));
if (!objValInt.IsNull())
{
    // Squirrel変数 valInt が存在する場合はここを通る
    valInt = objValInt.Cast<int>();
}

関数の取得と実行

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
-
!
-
|
!
 
-
!
-
|
|
-
|
!
|
!
// 受け取った文字列を改行付きで出力する関数
function printLine(str)
{
    print(str + "\n");
}
 
// 受け取った引数の合計値を返す関数
function calcTotal(...)
{
    local ret = 0;
    for (local i = 0; i < vargc; ++i)
    {
        ret += vargv[i];
    }
    return ret;
}

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
-
!
 
 
-
!
 
 
-
!
 
// 関数を取得
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

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

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

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

各クラスの説明

各クラスの説明のページにて。

*1  そういう例しか見たことがないのでそうだと思っているのですが、間違っているかも…。