toluaはC/C++をLuaにバインドするソースコードを生成するツールです。開発は2012年で止まっていますが、Lua5.2でも一応動作するようです。(この記事で扱っているのはtolua++ではありません)
数多あるC++テンプレートを駆使したライブラリとくらべて素朴感が漂うことが気に入っています。ピュアCでも大丈夫ですし、ビルド時間やコンパイル結果のサイズが小さくすむそうです。その代わりに、ビルドに「toluaを走らせる」というステップが増えます。
素朴な範囲(構築/破壊、メンバ呼び出し、演算子、ネームスペース程度)であればC++も対応しているようです。(DXライブラリやOpenGLのヘッダを乱暴に切り貼りしていますが問題なく動いてくれます)
手順
- LuaをリンクしたC言語のプロジェクトを用意します。
- Lua側に公開したい関数や構造体をリストにした、パッケージファイルというものを書きます。
- パッケージファイルを
tolua.exe
に通し、スタブ(C言語ソースファイル)を生成します。 - スタブと
tolua.h
をコンパイル対象に含めます。 - 動作を確認します。
ひと通りうまく行ったらスタブ生成も自動化すると捗ります。
大雑把な使い方
tolua一式の導入
まず、プロジェクトに以下のふたつを加えます。
- コンパイルした
tolua.exe
のバイナリ -
tolua.h
などのランタイム用のヘッダ
pkgファイルの用意
次にtoluaに読ませるバインディングの設定をするファイルを書きます。拡張子はpkg
が標準のようです。(以下パッケージファイルと呼びます)
たとえばhoge.pkg
という名前のファイルを作り、Lua側に公開したい関数や構造体をここに載せます。(これはごく簡単な例で、ヘッダファイルを読み込む機能もあるので安心してください)
typedef int Foo;
void foo_bar(Foo *);
スタブの生成
以下の様なコマンドでパッケージファイルをtolua.exe
に通します。C言語のスタブが出来上がります。(tolua_hoge.c
とします)
> tolua -o tolua_hoge.c hoge.pkg
すると、次のような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_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でいうと次のような依存関係です。
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
で手書きしたものも載せます。
<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はヘッダの内容を読みに行き、読み取れた全ての宣言をバインドします。
$#include "Cヘッダ.h"
Cのヘッダを部分的に読み込む
Cヘッダのうちtoluaに読ませる範囲を限定したい場合は、$hfile
を使って読み込みます。
$hfile "Cヘッダ.h"
Cヘッダ内部で、次のようにtoluaに読ませる部分をマーカーで囲みます。
// TOLUA_BEGIN
... toluaに読ませたい内容 ..
// TOLUA_END
外部のパッケージファイルを読み込む
pkg
ファイルが長くなってきた場合、分割することができます。
$pfile "他パッケージ.pkg"
その内容がその場に挿入されたものとして扱われます。
スタブにC記述を含める
$
を行頭に置くと、スタブの出力結果に生の記述を含めることができます。
defineやtypedefなど諸々のハックに便利です。
$ #define DEF_ModelID_H
$ typedef int ModelID;