きままにブログ

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

Luaの関数をC側で記録し、いつでも呼び出せるようにする

概要

例えば、関数myfuncをLuaで定義し、それを登録する関数registerMyFunctionおよび実行する関数doMyFunctionをC側で用意するとする。すなわち、テストコードは次の通り :

local myfunc = function()
	print("test_code");
end

registerMyFunction(myfunc);

doMyFunction();
doMyFunction();

想定される出力は当然、

test_code
test_code

である。

ポイント

ポイントは、与えられたデータをリファレンスに置き換えることで、その値をC側で保持し、利用することである。luaL_ref関数は、スタックトップにあるテーブル(値)のリファレンスをあるテーブルに記録し返す関数である。

そのあるテーブルとして、もちろんプログラム中に自分でグローバルなテーブルを作ってもよいが、あらかじめ用意されているレジストリ、それを表す疑似インデックスLUA_REGISTRYINDEXを用いることでそのような煩雑な作業を避けられる。

// スタックに関数を積む
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
// refに対応する関数をスタックに積む
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
// 関数を呼び出す
if(lua_pcall(L, 0, 0, 0) != LUA_OK) {
	throw std::exception(lua_tostring(L, -1));
}

実行例

#include <iostream>
#include <Lua53/lua.hpp> // 自分で勝手に登録したヘッダです

int g_ref;

int L_registerMyFunction(lua_State* L) {
	g_ref = luaL_ref(L, LUA_REGISTRYINDEX);
	return 0;
}
int L_doMyFunction(lua_State* L) {
	lua_rawgeti(L, LUA_REGISTRYINDEX, g_ref);

	if(lua_pcall(L, 0, 0, 0) != LUA_OK) {
		throw std::exception(lua_tostring(L, -1));
	}

	return 0;
}

int main() {
	auto L = luaL_newstate();

	luaL_openlibs(L);

	lua_pushcfunction(L, L_registerMyFunction);
	lua_setglobal(L, "registerMyFunction");
	lua_pushcfunction(L, L_doMyFunction);
	lua_setglobal(L, "doMyFunction");

	try {
		if(luaL_loadfile(L, "lua.lua") || lua_pcall(L, 0, 0, 0)) {
			throw std::exception(lua_tostring(L, 1));
		}
	}
	catch(std::exception& e) {
		std::cout << e.what() << std::endl;
	}

	std::cin.get();
	return 0;
}

グローバル変数がナウくない?

前回記事の通り、上位値を登録できるようにしましょう。ただ、オブジェクトを作るごとに関数を登録するのがいけてない…

#include <iostream>
#include <Lua53/lua.hpp>

class MyClass {
public:
	static int L_constructor(lua_State* L) {
		const luaL_Reg funcs[] = {
			{ "register", L_register },
			{ "execute", L_execute },
			{ NULL, NULL }
		};

		luaL_newlibtable(L, funcs);
		auto self = static_cast<MyClass*>(lua_newuserdata(L, sizeof(MyClass)));
		new(self)MyClass(L);
		luaL_setfuncs(L, funcs, 1);
		
		return 1;
	}

	MyClass(lua_State* L) : L(L), ref(0) {
		
	}

	static int L_register(lua_State* L) {
		auto self = static_cast<MyClass*>(lua_touserdata(L, lua_upvalueindex(1)));
		self->ref = luaL_ref(L, LUA_REGISTRYINDEX);
		return 0;
	}

	static int L_execute(lua_State* L) {
		auto self = static_cast<MyClass*>(lua_touserdata(L, lua_upvalueindex(1)));
		lua_rawgeti(L, LUA_REGISTRYINDEX, self->ref);

		if(lua_pcall(L, 0, 0, 0) != LUA_OK) {
			throw std::exception(lua_tostring(L, -1));
		}

		return 0;
	}

	int ref;
	lua_State* L;
};


int main() {
	auto L = luaL_newstate();

	luaL_openlibs(L);

	lua_pushcfunction(L, MyClass::L_constructor);
	lua_setglobal(L, "createMyClass");

	try {
		if(luaL_loadfile(L, "lua.lua") || lua_pcall(L, 0, 0, 0)) {
			throw std::exception(lua_tostring(L, 1));
		}
	}
	catch(std::exception& e) {
		std::cout << e.what() << std::endl;
	}

	std::cin.get();
	return 0;
}