C++のクラスをバインドする


// テストクラス class Test { int _x; public: Test(int x) : _x(x) { printf("ctor: %p, %d\n", this, _x); } ~Test() { printf("dtor: %p\n", this); } void talk() { printf("talk(), %p, %d\n", this, _x); } void *operator new ( size_t sz, void *mem ) { return mem; } };
メソッドをコールするために lua へバインドするための関数でラップする。
static int l_talk(lua_State* L) { // ユーザデータ(ポインタ)を Lua Script からもらう。 Test* p = (Test*)lua_touserdata(L, -1); // メソッド本体をコール p->talk(); // 戻り値の数 return 0; }
Lua から new をしたら, C++ 側では実際にオブジェクトを作成する。 さらにメソッドをフィールドアクセスの構文( p.method() )で呼べるように __index を指定したメタテーブルをオブジェクトに設定する。
int l_new( lua_State *s ) { // Lua 側から数値パラメータを一個もらう。 int i = lua_tonumber( s, -1 ); // オブジェクトを new して、そのポインタを Luaスタックに積む。 void *p = lua_newuserdata ( s, sizeof( Test ) ); void *q = new(p) Test( i ); // クロージャで関連づけたテーブルを取り出し、 lua_pushvalue( s, lua_upvalueindex(1)); // メタテーブルとして設定する lua_setmetatable( s, -2); // ポインタを Lua Script 側へ返す。 // これで Lua 側からオブジェクトをハンドルできる。 return 1; }
Lua からは次のように作成する。
-- オブジェクトを生成して、ハンドルをもらう。 -- Test テーブルの new メソッドをコールする。 local p = Test.new( 5 ); -- 操作したいハンドルを指定してメソッドをコールする。 -- オブジェクト p に対して p.talk と書けるのはフィールドアクセス( __index )をオーバーライドしたから p.talk( p ); -- : を利用することで、第一引数を省略できメソッド的な記述ができる。 -- やっているのは上と同じ。 p:talk(); -- 別オブジェクトをつくる。 local p2 = Test.new( 3 );
void defclass( lua_State *s ) { // クラスをテーブルとして生成 lua_newtable( s ); // ユーザデータで共有するメタテーブル用テーブル作成 // インスタンス毎に setmetatable される lua_newtable( s ); // __index をオーバーライドすることで obj.method() という記述した( フィールドアクセス )時に // メソッド用関数のテーブルを参照させる。 // setfield によって値がポップするためコピーしておく。 lua_pushvalue(s, -1); // メタテーブル自身 lua_setfield( s, -2, "__index"); // クラスのメソッドをテーブルを登録しておく lua_pushcfunction( s, l_talk ); lua_setfield( s, -2, "talk"); // テーブルを上位値としてクロージャ生成 // 後で new() をした時にテーブルを取り出し instance のメタテーブルとして設定する。 lua_pushcclosure( s, l_new, 1); lua_setfield( s, -2, "new"); // 生成したクラス(テーブル)を Test という名前でグローバル変数に公開 lua_setglobal( s, "Test"); }