toluaでluaバインディング

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

数多あるC++テンプレートを駆使したライブラリとくらべて素朴感が漂うことが気に入っています。ピュアCでも大丈夫ですし、ビルド時間やコンパイル結果のサイズが[小さくすむ](http://realmensch.org/blog/fun-lua-bindings)そうです。その代わりに、ビルドに「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側に公開したい関数や構造体をここに載せます。(これはごく簡単な例で、ヘッダファイルを読み込む機能もあるので安心してください)

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

## スタブの生成

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

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

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

```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`と同じ調子で呼び出せばバインド完了です。

```c: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でいうと次のような依存関係です。

```mf: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`で手書きしたものも載せます。

```xml: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++通り構造体や関数を宣言できますが、`$` を行頭に持ついくつかの特殊命令が使えます。

この辺りはほとんど[ドキュメント](http://www.tecgraf.puc-rio.br/~celes/tolua/tolua-3.2.html)がないので、[ソース](https://github.com/LuaDist/tolua/blob/master/src/bin/lua/package.lua#L37)を読んで推測しました。

## Cのヘッダを取り込む ##

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

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

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

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

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

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

```c:hoge.pkg
$hfile "Cヘッダ.h"
```

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

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

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

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

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

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

## スタブにC記述を含める ##

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

```c:hoge.pkg
$ #define DEF_ModelID_H
$ typedef int ModelID;
```