toluaでluaバインディング

  • 10
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

toluaはC/C++をLuaにバインドするソースコードを生成するツールです。開発は2012年で止まっていますが、Lua5.2でも一応動作するようです。(この記事で扱っているのはtolua++ではありません)

数多あるC++テンプレートを駆使したライブラリとくらべて素朴感が漂うことが気に入っています。ピュアCでも大丈夫ですし、ビルド時間やコンパイル結果のサイズが小さくすむそうです。その代わりに、ビルドに「toluaを走らせる」というステップが増えます。

素朴な範囲(構築/破壊、メンバ呼び出し、演算子、ネームスペース程度)であればC++も対応しているようです。(DXライブラリやOpenGLのヘッダを乱暴に切り貼りしていますが問題なく動いてくれます)

手順

  1. LuaをリンクしたC言語のプロジェクトを用意します。
  2. Lua側に公開したい関数や構造体をリストにした、パッケージファイルというものを書きます。
  3. パッケージファイルをtolua.exeに通し、スタブ(C言語ソースファイル)を生成します。
  4. スタブとtolua.hをコンパイル対象に含めます。
  5. 動作を確認します。

ひと通りうまく行ったらスタブ生成も自動化すると捗ります。

大雑把な使い方

tolua一式の導入

まず、プロジェクトに以下のふたつを加えます。

  1. コンパイルした tolua.exe のバイナリ
  2. tolua.h などのランタイム用のヘッダ

pkgファイルの用意

次にtoluaに読ませるバインディングの設定をするファイルを書きます。拡張子はpkgが標準のようです。(以下パッケージファイルと呼びます)

たとえばhoge.pkgという名前のファイルを作り、Lua側に公開したい関数や構造体をここに載せます。(これはごく簡単な例で、ヘッダファイルを読み込む機能もあるので安心してください)

hoge.pkg
typedef int Foo;
void foo_bar(Foo *);

スタブの生成

以下の様なコマンドでパッケージファイルをtolua.exeに通します。C言語のスタブが出来上がります。(tolua_hoge.c とします)

make-stub.bat
> tolua -o tolua_hoge.c hoge.pkg

すると、次のようなCソースが生成されます。

tolua_hoge.c
/*
** Lua binding: hoge
*/

#include "tolua.h"

#ifndef __cplusplus
#include <stdlib.h>
#endif
#ifdef __cplusplus
 extern "C" int tolua_bnd_takeownership (lua_State* L); // from tolua_map.c
#else
 int tolua_bnd_takeownership (lua_State* L); /* from tolua_map.c */
#endif
#include <string.h>

/* Exported function */
TOLUA_API int tolua_hoge_open (lua_State* tolua_S);
LUALIB_API int luaopen_hoge (lua_State* tolua_S);


/* function to register type */
static void tolua_reg_types (lua_State* tolua_S)
{
}

/* function: foo_bar */
static int tolua_hoge_foo_bar00(lua_State* tolua_S)
{
#ifndef TOLUA_RELEASE
 tolua_Error tolua_err;
 if (
 !tolua_isnumber(tolua_S,1,0,&tolua_err) ||
 !tolua_isnoobj(tolua_S,2,&tolua_err)
 )
 goto tolua_lerror;
 else
#endif
 {
  Foo tolua_var_1 = ((Foo)  tolua_tonumber(tolua_S,1,0));
 {
  foo_bar(&tolua_var_1);
 tolua_pushnumber(tolua_S,(lua_Number)tolua_var_1);
 }
 }
 return 1;
#ifndef TOLUA_RELEASE
 tolua_lerror:
 tolua_error(tolua_S,"#ferror in function 'foo_bar'.",&tolua_err);
 return 0;
#endif
}

/* Open lib function */
LUALIB_API int luaopen_hoge (lua_State* tolua_S)
{
 tolua_open(tolua_S);
 tolua_reg_types(tolua_S);
 tolua_module(tolua_S,NULL,0);
 tolua_beginmodule(tolua_S,NULL);
 tolua_function(tolua_S,"foo_bar",tolua_hoge_foo_bar00);
 tolua_endmodule(tolua_S);
 return 1;
}
/* Open tolua function */
TOLUA_API int tolua_hoge_open (lua_State* tolua_S)
{
 lua_pushcfunction(tolua_S, luaopen_hoge);
 lua_pushstring(tolua_S, "hoge");
 lua_call(tolua_S, 1, 0);
 return 1;
}

このtolua_hoge.cをランタイムヘッダ(tolua.hその他)と共にコンパイルに含めます。

C側からリンク

ファイル名が hoge.pkg の場合、スタブからは int tolua_hoge_open(lua_state *S) という関数がエクスポートされます。

これをluaの初期化時luaL_openlibsと同じ調子で呼び出せばバインド完了です。

Luaの初期化
lua_State *make_lua_state(void) {
    lua_State *L = luaL_newstate();

    if (L != NULL) {
        luaL_openlibs(L);
        tolua_hoge_open(L);
    }

    return L;
}

スタブ生成手順をビルドに含める

たとえば、luabind.pkgだけを使って、中でluabind_glu.pkgやC言語側のヘッダの類を読み込んでいるとします。makefileでいうと次のような依存関係です。

hoge.dep
luabind.inc: luabind.pkg
    ./tolua luabind.inc -o luabind.pkg

luabind.pkg: luabind_glu.pkg console.h camera.h room.h

# #include "luabind.inc" と幾つか微調整の小細工をするCファイル
luabind.c: luabind.inc

make系の場合はただtoluaコマンドの呼び出しを入れるだけです。MSVCのvcxprojで手書きしたものも載せます。

hoge.vcxproj
  <Target Name="MakeLuaHeader"
          Inputs="luabind.pkg;luabind_opengl.pkg;console.h;camera.h;room.h"
          Outputs="luabind.h"
          BeforeTargets="ClCompile">
    <Exec Command="tolua.exe -o luabind.h luabind.pkg"
          WorkingDirectory="$(SolutionDir)"
          IgnoreExitCode="true" />
  </Target>

Inputsの方にはパッケージファイル内で取り込んでいるヘッダも載せておくとヘッダの変更時にスタブも再生成してくれます。makefileならきちんと依存関係も書くところですがサボりました。

パッケージ内の特殊命令

パッケージファイルでは通常のC++通り構造体や関数を宣言できますが、$ を行頭に持ついくつかの特殊命令が使えます。

この辺りはほとんどドキュメントがないので、ソースを読んで推測しました。

Cのヘッダを取り込む

Luaに関数を持ち込むたびに宣言をヘッダからpkgにコピーしてくるのは面倒なことでしょう。

素朴な範囲であればtoluaはC++のサブセットを読み込むことができます。

こう書くと、toluaはヘッダの内容を読みに行き、読み取れた全ての宣言をバインドします。

hoge.pkg
$#include "Cヘッダ.h"

Cのヘッダを部分的に読み込む

Cヘッダのうちtoluaに読ませる範囲を限定したい場合は、$hfileを使って読み込みます。

hoge.pkg
$hfile "Cヘッダ.h"

Cヘッダ内部で、次のようにtoluaに読ませる部分をマーカーで囲みます。

hoge.pkg
// TOLUA_BEGIN
... toluaに読ませたい内容 ..
// TOLUA_END

外部のパッケージファイルを読み込む

pkgファイルが長くなってきた場合、分割することができます。

hoge.pkg
$pfile "他パッケージ.pkg"

その内容がその場に挿入されたものとして扱われます。

スタブにC記述を含める

$ を行頭に置くと、スタブの出力結果に生の記述を含めることができます。
defineやtypedefなど諸々のハックに便利です。

hoge.pkg
$ #define DEF_ModelID_H
$ typedef int ModelID;