Wednesday, February 18, 2015

自力Luaバインド 決定版

前回の続きです。クロージャでクラスのインスタンス生成をすっきり書けそうなのでやってみました。
#include <new>
extern lua_State *L;
class MyClass
{
public:
MyClass()
{
value = 0;
puts("MyClass::MyClass called");
}
~MyClass()
{
puts("MyClass::~MyClass called");
}
int value;
};
static const char* myClassName = "MyClass";
#define GET_MYCLASS \
MyClass* p = (MyClass*)luaL_checkudata(L, 1, myClassName); \
if (!p) { return 0; }
void BindMyClass()
{
static struct luaL_Reg methods[] =
{
{ "__gc", [](lua_State* L) { GET_MYCLASS p->~MyClass(); return 0; } },
{ "SetValue", [](lua_State* L) { GET_MYCLASS p->value = (int)lua_tointeger(L, -1); return 0; } },
{ "GetValue", [](lua_State* L) { GET_MYCLASS lua_pushinteger(L, p->value); return 1; } },
{ nullptr, nullptr },
};
BindClass(L, myClassName, methods, [](lua_State* L) { new (lua_newuserdata(L, sizeof(MyClass))) MyClass; return 1; });
}
ここまでシンプルにかければ、バインドの為の外部ライブラリの力を借りずとも十分やっていけそうです。BindClassという新たな関数が追加され、こちらに雑多なコードが移動しています。BindClassがuserdataを生成するだけの関数(ラムダで定義)を引数としている点がポイントです。ラムダでは前回同様userdata上にplacement newでインスタンスを初期化します。
static int CreateCppClassInstance(lua_State* L)
{
const char* className = lua_tostring(L, lua_upvalueindex(1));
lua_CFunction actualInstanceCreator = lua_tocfunction(L, lua_upvalueindex(2));
actualInstanceCreator(L);
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
return 1;
}
static void CreateClassMetatable(lua_State* L, const char* className, luaL_Reg methods[])
{
luaL_newmetatable(L, className);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3);
luaL_setfuncs(L, methods, 0);
lua_pop(L, 1);
}
static void CreateClassInstanceCreator(lua_State* L, const char* className, lua_CFunction creator)
{
lua_pushstring(L, className);
lua_pushcfunction(L, creator);
lua_pushcclosure(L, CreateCppClassInstance, 2);
lua_setglobal(L, className);
}
void BindClass(lua_State* L, const char* className, luaL_Reg methods[], lua_CFunction creator)
{
CreateClassMetatable(L, className, methods);
CreateClassInstanceCreator(L, className, creator);
}
BindClassもこれだけで、特定のクラス名などが取り除かれ汎用化されています。CreateClassMetatableでメタテーブルを生成するのは定番のコードです。

C++にだけ慣れていると奇妙に見えるのはCreateCppClassInstanceです。これはLuaから呼べる関数型をしていますが単独では呼べません。upvalueという領域にクラス名とクラスのインスタンス生成を行う関数を結び付けてクロージャを生成しておくことで初めて関数として呼べるようになっています。そのクロージャを生成してクラス名と同名のグローバル関数を登録する部分がCreateClassInstanceCreatorです。

このようにしてクラスの種類数だけクロージャを生成することで、どんな種類のクラスインスタンスもCreateCppClassInstanceに生成させることが出来るようになります。

ところで、CreateCppClassInstanceのactualInstanceCreatorはlua_CFunction型として取り出して、直接CからactualInstanceCreatorを呼び出しています。これはBindClassの引数として受け取っているcreatorです。ということは、Luaが直接creatorを呼び出すことは無いのでlua_CFunction型を使う必要は無いということです。lightuserdataとして任意の型の関数にしても問題なさそうです。

No comments:

Post a Comment