Lua始めました

いやかなり前からぐだぐだやってはいたんだけどね…。
このブログは元々プログラムでぐだぐだやったことを誰かの役に立つかもしれないから書きとめておこうと立ち上げたわけでですね…。


まずはLuaのC APIの使い方を間違えたところ何も言わずにアプリケーションが終了、ついでにWinMainの最初に仕掛けておいた

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

によって大量のメモリリーク報告が(VCのメモリリーク検出機能でググれ)。まあ昔こういう挙動に出くわした経験はあったわけでですね、LuaリファレンスマニュアルによるとLuaがぶっ壊れた場合はパニック関数を呼び、その後exit(EXIT_FAILURE)を呼ぶそうです。このexit、デバッガで1行ずつ実行すると突然何も言わずに落ちるんだよね…。昔も見たってことは結構よくある実装なんだろうか。当時はmainを持たないライブラリがexitを呼ぶのはどうかと思ったもんだが…。メモリリークはローカル変数のデストラクタが呼ばれなかったことによるものでした。パニック関数はlua_atpanicで変更できるとのこと。エラーメッセージがスタックトップから得られる。多分デフォルトはエラーメッセージをstderrにでも流してるんだろうけど、リダイレクトもしてないしWindowsアプリからじゃ見れねーよバカヤロー。このパニック関数を読んだ後exit(EXIT_FAILURE);するのでlongjmpなどで戻らないようにすれば終了しないようにできる、とのこと。というわけで例外をぶっぱ。

int atpanic(lua_State *L)
{
	throw std::runtime_error(lua_tostring(L, -1));
}

main {
	try {
		...
		lua_atpanic(L, atpanic);
		...
	} catch (const dx9lib::Dx9libError &e) {
		debug.print("Error: ");
		debug.println(e.what());
	} catch (const std::runtime_error &e) {
		debug.print("Error: ");
		debug.println(e.what());
	}
}

例外クラス作るのも面倒だったのでとりあえずruntime_errorで。スタックトップのエラーメッセージをそのまま例外にのせて投げる。ゆくゆくはruntime_errorからlua_errorを派生させてもいいかもしれんね。main(C++) > Lua関数(C) > panic関数(C++) の呼び出し構造からmainに向かって例外を投げるのでちょっと不安があったが問題なし。


とりあえずLua標準のprint関数では出力が見れないので(リダイレクトすればいけるけど)、自作のデバッグ出力クラスに出力する関数をLuaにエクスポートしてみる。

int le_trace(lua_State *L)
{
	int n = lua_gettop(L);
	for (int i = 1; i <= n; i++) {
		lua_getglobal(L, "tostring");
		lua_pushvalue(L, i);
		lua_pcall(L, 1, 1, 0);
		const char *str = lua_tostring(L, -1);
		debug.print(str);
		lua_pop(L, 1);
	}
	debug.println("");
	return 0;
}

leはLuaExportの略らしいです。引数をlua_tostring(C)でスタックから取得すると文字列か数値でないといけないとか書いてあったので、print(Lua)の解説を参考にtostring(Lua)関数で文字列に変換することにしました。ついでにprint(Lua)の仕様を参考に引数は何個でもいいようにしました。
まずlua_gettop(C)でスタックサイズnを得ます。これは引数の個数と等しいです。次にiを1からnまで変化させます。正のインデックスはスタックの底からのインデックスです。1は最初にpushされた第1引数です。この値を引数に、tostring(Lua)関数を呼びます。関数の呼び出し方はまず関数を積み、次に引数を左から順番に積み、lua_pcallを引数と返り値の数を指定しながら呼び出します。ということでlua_getglobal(C)で"tostring"の名前をもつグローバル変数(値は関数)をスタックに積み、lua_pushvalue(C)でスタックのi番目の値(第i引数)を第1引数としてpushします。そしてpcall。エラーハンドラは今のところスルーで。すると関数と引数がすべてpopされ、返り値がpushされます。変換後の文字列が1個返ってきますから、lua_tostring(C)でconst char *を得ます。インデックスは上から1番目ということで-1です。これを出力した後、このまま次のループに進むとスタックが1つずつ伸びていってしまうので、lua_pop(C)でpopします。
このC-Lua間で値の受け渡しに使われるスタックは関数終了時に上から関数の返り値個がLuaへの返り値となり、その他は捨てられるとか。機械語/アセンブラで使うスタックと違って前後でぴったり合わせる必要は別にないのが変な感じがしますがC関数呼び出しごとに1個作られて終わったら捨てられる適当なものなので要は慣れですね。