LuaをC/C++に組み込む

昔Wikiに書いていたものから、比較的まともに書けてそうなものを持ってきました。2007年当時のままの記載のため、今も使えるかどうかは知りません。

Luaの開始・終了

Luaの使用を開始するには関数/lua_openや関数/luaL_newstate(両者は全く同じ内容)、関数/lua_newstateを使う。
最近のバージョンではlua_openに引数を渡す必要がなくなったらしい。
これらの関数の戻り値を型/lua_Stateのポインタとして受け取り、以後このポインタを使って各種の操作をする。

lua_State *L = lua_open();

使用を終了するときは関数/lua_closeを使う。
使わなくてもOSが何とかしてくれることもあるらしいが自分でopenしたものは自分で責任もってcloseしよう。

lua_close(L);

スレッドを終了する

とは言いましても、スレッドを個別に終了する方法は提供されていないわけです。
すなわち、スレッドを終了させることができるのは実際に動いているLuaのスレッド自身だけなのです。
だからといってスレッドを停止させずにいると無限ループ構造になっているスレッドだと新しいスレッドを作るたびにメモリを食うわけで。

--無限ループを持つスレッド(Lua)
function mythread()
	while true do
		dosomething()
		coroutine.yield()
	end
end

例えば敵キャラの思考ルーチンだと敵が現れるたびに新しいスレッドが作成され、敵が死ぬたびにスレッドが不必要になるわけです。
これをスレッドが不必要になっても停止させずにいたら、弾幕シューティングとかだとどえらいことになります。
そこで、Cだけで何とかしようとは考えずに、Luaスクリプトと協調して、スレッドが不必要になったらLuaのスレッドに終了の通知を出して、それを受け取ったLuaスクリプトのスレッドが自発的に実行を終了するようにするのです。

/* CからLuaのスレッドに制御を渡す部分(C) */
lua_pushboolean(thread, isdead);
lua_resume(thread, 1);
--無限ループを持つスレッド(Lua)
function mythread()
	while true do
		dosomething()
		if coroutine.yield() then return end
	end
end

こうしておくと、C側でisdeadがtrueになったときにスレッドが終了してガベージコレクションの対象となるわけです。

…なにかもっといい方法ありませんかねぇ…。

変数の値を設定する

関数/lua_getglobalや[関数/lua_getfieldを使って目的の変数(テーブルの要素)が入っているテーブルをスタックに積む](グローバル変数ならばこの作業は不要)。
目的のテーブルをスタックに積んだら関数/lua_pushnumberや関数/lua_pushstringなどを使ってスタックトップに新しい値を積む。
この状態で関数/lua_setfield(グローバル変数なら関数/lua_setglobal)を使えば目的の変数に値が代入される。
変数の値を設定した後は忘れずに関数/lua_popなどでスタックを元に戻そう。
(もちろん元に戻さず続いて別の値を設定するのもあり)

#include <stdarg.h>

// SetNumber(L, num, 3, "mifumi", "bust", "size");
// とすると、mifumi.bust.sizeにはnumの数値が入るというもの(argcは続く引数の数)
void SetNumber(lua_State *L, lua_Number value, int argc, ...)
{
	va_list vl;
	va_start(vl, argc);
	int idx = LUA_GLOBALSINDEX;
	for (int i=0; i<argc-1; i++) {
		lua_getfield(L, idx, va_arg(vl, char*));
		idx=-1;
	}
	lua_pushnumber(L, value);
	lua_setfield(L, argc>1?-2:LUA_GLOBALSINDEX, va_arg(vl, char*));
	va_end(vl);
	lua_pop(L, argc-1);
}

変数の値を得る

関数/lua_getglobalを使うとグローバル変数がスタックに積まれる。
スタックに積まれた値を使って関数/lua_getfieldを使ってやるとテーブルの中身が更にスタックに積まれる。
スタックに積まれたLuaの値を日頃馴染み深い数値や文字列に変換するには関数/lua_tonumberや関数/lua_tostringなどを使う。
変数の値を得た後は忘れずに関数/lua_popなどでスタックを元に戻そう。
(もちろん元に戻さず続いて別の値を得るのもあり)

#include <stdarg.h>

// lua_Number num = GetNumber(L, 3, "mifumi", "bust", "size");
// とすると、numにはmifumi.bust.sizeの数値が入るというもの(argcは続く引数の数)
lua_Number GetNumber(lua_State *L, int argc, ...)
{
	va_list vl;
	va_start(vl, argc);
	int idx = LUA_GLOBALSINDEX;
	for (int i=0; i<argc; i++) {
		lua_getfield(L, idx, va_arg(vl, char*));
		idx=-1;
	}
	va_end(vl);
	lua_Number ret = lua_tonumber(L, -1);
	lua_pop(L, argc);
	return ret;
}