// テストクラス
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");
}