LuaとC++の連携
Luaのプログラム例
まずはLuaで次のようなことをしてみたいと思う。
moc = MOC.new(); moc2 = MOC.new(); function func(x) setNumber(x); moc:set(x); moc2:set(x * 2); return x * x; end function call() x = getNumber(); print("x is " .. x .. "\n"); x = moc:get(); print("(moc) x is " .. x .. "\n"); x = moc2:get(); print("(moc2) x is " .. x .. "\n"); end
MOCはMyOpenClassの略で、自作クラスだと思っていただきたい。これをLuaのテーブルのnewを関数呼び出しすることで新しくインスタンスを生成し、それをmocとする。
次に関数funcを自分で定義する。これはC++から呼び出すものである。そしてcallも定義する。こちらも呼び出されるだけである。
さて、まずC++からfuncに引数アリで呼び出す。このとき、setNumberで値を記憶する。次にmoc, moc2の〈メンバ関数〉setにx, 2 * xを渡して記憶させる。
次にcallをC++から呼び出す。setで記憶した数値を掃出し、xを表示する。moc, moc2で設定した値もあわせて取得し、表示する。
C++のプログラム例
汎用Lua
必要なら随時追加していくが、とりあえずどのLuaプログラムでも共通して使えるクラスを作った。
class Lua { public: Lua() { // lua_Stateの生成 L = luaL_newstate(); // Lua標準ライブラリの読み込み luaL_openlibs(L); } ~Lua() { lua_close(L); } protected: void registerClosure(lua_CFunction function, const char* name, void* value) const { // 値をプッシュし lua_pushlightuserdata(L, value); // その値を元にするクロージャを生成し lua_pushcclosure(L, function, 1); // そのクロージャをグロバール変数nameとする lua_setglobal(L, name); } void loadFile(const char* file_name) const { if(luaL_loadfile(L, file_name) || lua_pcall(L, 0, 0, 0)) { throw exception(lua_tostring(L, 1)); } } lua_State* L; };
拡張Lua
上記のクラスLuaを継承して様々な関数等を追加していく。
class MyLua : public Lua { public: MyLua() : Lua() { registerClosure(L_SetNumber, "setNumber", this); registerClosure(L_GetNumber, "getNumber", this); MyOpenClass::Register(L); loadFile("sample.lua"); } ~MyLua() { } int func(int x) { lua_getglobal(L, "func"); lua_pushinteger(L, x); if(lua_pcall(L, 1, 1, 0) != 0) { throw exception(lua_tostring(L, 2)); } if(lua_isnumber(L, -1)) { int ret = lua_tointeger(L, -1); return ret; } else { throw exception("関数funcは整数を返さなければならない。"); } } void call() { lua_getglobal(L, "call"); if(lua_pcall(L, 0, 0, 0) != 0) { throw exception(lua_tostring(L, 2)); } } static int L_SetNumber(lua_State* L) { MyLua* self = *(MyLua**)lua_touserdata(L, lua_upvalueindex(1)); int number = lua_tointeger(L, -1); self->number = number; cout << "SET NUMBER " << number << endl; return 0; } static int L_GetNumber(lua_State* L) { MyLua* self = *(MyLua**)lua_touserdata(L, lua_upvalueindex(1)); cout << "GET NUMBER" << endl; lua_pushinteger(L, self->number); return 1; } private: int number; };
C++からLuaのfunc, callを呼び出せるように関数を定義し、また、LuaからC++の関数を呼び出せるように登録した。
MyOpenClass::Register(L);では、LuaにMyOpenClassを登録するような処理を記述した。そのクラスは次の通り。
class MyOpenClass { public: MyOpenClass() { cout << "create MyOpenClass" << endl; } ~MyOpenClass() { cout << "delete MyOpenClass" << endl; } void set(int x) { cout << "MyOpenClass::set(" << x << ")" << endl; this->x = x; } int get() { cout << "MyOpenClass::get()" << endl; return x; } public: static void Register(lua_State* L) { const luaL_Reg funcs[] = { { "new", MyOpenClass::L_Construct }, { "delete", MyOpenClass::L_Destruct }, { NULL, NULL } }; // 新しいテーブルを作り、funcsを登録 luaL_newlib(L, funcs); // そのテーブルをグローバル変数MOCとする。 lua_setglobal(L, "MOC"); } static int L_Construct(lua_State* L) { // 1 MyOpenClass** ref_self = (MyOpenClass**)lua_newuserdata(L, sizeof(MyOpenClass*)); *ref_self = new MyOpenClass(); // 2 lua_newtable(L); // 3 const luaL_Reg funcs[] = { { "get", MyOpenClass::L_Get }, { "set", MyOpenClass::L_Set }, { NULL, NULL } }; luaL_newlib(L, funcs, 1); // 3のデータを2の__indexに入れる(=メソッド探索に追加する) lua_setfield(L, 2, "__index"); // 3 lua_pushcfunction(L, MyOpenClass::L_Destruct); // 3のデータを2の__gcに入れる(=ガベージコレクタを指定) lua_setfield(L, 2, "__gc"); // 2のテーブルを1のメタテーブルとする lua_setmetatable(L, 1); return 1; } static int L_Destruct(lua_State* L) { MyOpenClass** ref_self = (MyOpenClass**)lua_touserdata(L, 1); delete *ref_self; return 0; } static int L_Set(lua_State* L) { MyOpenClass* self = *(MyOpenClass**)lua_touserdata(L, 1); int x = lua_tointeger(L, -1); self->set(x); return 0; } static int L_Get(lua_State* L) { MyOpenClass* self = *(MyOpenClass**)lua_touserdata(L, 1); int x = self->get(); lua_pushinteger(L, x); return 1; } private: int x; };
void Register(lua_State* L)ではグローバル変数MOCを登録した。この変数はテーブルで、new, deleteメソッドを持つ。
newメソッドの正体はint L_Construct(lua_State* L)であり、C++側のget, setメソッドを持つ。このようにインスタンスを作るたびにメソッド用(ポインタの)メモリを食いつぶすので大量のオブジェクトを作る場合は、オブジェクトのデータだけをユーザーデータにし、関数に渡して処理するほうがいい。
なおこのオブジェクトはガベージコレクタが実行されると自動的に生成したオブジェクトを破棄するようにしてある。