きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

クロージャーを纏めて登録

C++の関数をLuaから呼び出す

C++Luaから呼び出す際、C++側のクラスのなかのメソッドを呼び出したい場合、そのクラスのポインタが必要となる。グローバル変数に代入するのは気持ちが悪いので、Luaクロージャーにポインタを記憶させ、関数が呼ばれるたびにそこから引っ張りだすことを考える。

結論から言うと次の通り:

#include <iostream>
#include <Lua53beta\lua.hpp>
#include <exception>

using namespace std;

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;
};

class MyLua : public Lua {
public:
	MyLua() : Lua() {
		const luaL_Reg funcs[] = {
			{ "func", MyLua::L_Function },
			{ "func2", MyLua::L_Function2 },
			{ NULL, NULL }
		};

		luaL_newlibtable(L, funcs);
		lua_pushlightuserdata(L, this);
		luaL_setfuncs(L, funcs, 1);
		
		lua_setglobal(L, "test");

		loadFile("sample.lua");
	}

	static int L_Function(lua_State* L) {
		MyLua* self = (MyLua*)lua_touserdata(L, lua_upvalueindex(1));

		int number = lua_tointeger(L, -1);
		self->number = number;

		cout << "Function " << number << endl;

		return 0;
	}

	static int L_Function2(lua_State* L) {
		MyLua* self = (MyLua*)lua_touserdata(L, lua_upvalueindex(1));

		cout << "Function2 " << self->number << endl;

		return 0;
	}

private:
	int number;
};

using namespace std;

int main() {
	try {
		MyLua mylua;
	}
	catch(const std::exception& e) {
		cout << e.what() << endl;
	}
	cin.get();
}

自分用のライブラリ空間testを作り、test.func(10); test.func2();などとして呼び出す。

luaL_newlibtable(L, funcs);
lua_pushlightuserdata(L, this);
luaL_setfuncs(L, funcs, 1);
		
lua_setglobal(L, "test");

の部分がライブラリを作る部分である。関数群funcを登録するのだが、luaL_newlibtableで空間を予約し、次にスタックにこのクラスのポインタthisをLightUserDataとしてプッシュする。そしてsetfuncsを呼び出し、そのスタックのトップにある値1個分を記録させ、その前のライブラリのテーブルに関数群を登録し、関数群funcsが呼び出されるたびに上記の値をアップバリュー(上位値)として取得できるようにする。

最後にそのライブラリテーブルにグローバル名testを付けて、いつでも呼び出されるようにするという寸法だ。

いざ関数が呼び出されると、

MyLua* self = (MyLua*)lua_touserdata(L, lua_upvalueindex(1));

によってポインタを取得できる。lua_upvalueindexは上位値のインデックスに変換する関数で、今回は1つしかないので1を指定してある。2つ以上ある場合はここの数字を変えて取り出す。

なお引数は lua_tointeger(L, -1) のように普通に取り出していく。もちろん-1でなくて1でも動く。