tolua++で登録したc関数にluaのコールバック関数を渡す方法を探す
こちらの記事
http://blogs.wankuma.com/izmktr/archive/2009/04/06/170817.aspx
を参考にやってみた。
謎のエラーに襲われるなど紆余曲折を経てとりあえず目的を達成することができた。美しくない方法だけど。
まず、やりたいこと。
-- test.lua -- toluaで公開したc++クラス func=c.FrameFunc:new() -- luaの関数を渡す func:setOnFrame(function() print('OnFrame') end) -- このloopはc++側にあってもいい local frame=0 while true do -- 中でsetOnFrameで登録したLua関数を呼び出す func:onFrame() frame=frame+1 if frame>10 then break end end
c++のクラス
// framefunc.h #ifndef FRAME_FUNC_H #define FRAME_FUNC_H #include <iostream> class FrameFunc { lua_State *L_; // レジストリに登録したLua関数へのキー int key_; public: FrameFunc() : L_(0), key_(0) {} // setOnFrameから呼ばれる void FrameFunc::setLuaFunctionKey(lua_State *L, int key) { // key は無引数、無返値の関数へのレジストリキー L_=L; key_=key; } // 保持しているLua関数を実行する void FrameFunc::onFrame() { int base=lua_gettop(L_); lua_rawgeti(L_, LUA_REGISTRYINDEX, key_); if(lua_isfunction(L_, -1)){ if(lua_pcall(L_, 0, 0, 0)!=0){ std::cout << lua_tostring(L_, -1) << std::endl; } } lua_settop(L_, base); } static int FrameFunc::setOnFrame(lua_State *L) { // luaスクリプトから呼ばれる。第1引数にFrameFuncのインスタンスが // 入っている。 FrameFunc *self=(FrameFunc*)tolua_tousertype(L, 1, 0); int key=luaL_ref(L, LUA_REGISTRYINDEX); self->setLuaFunctionKey(L, key); return 0; } }; #endif // FRAME_FUNC_H
tolua用
// framefunc.pkg $#include "framefunc.h" module c { class FrameFunc { FrameFunc(); void onFrame(); }; }
tolua実行
> tolua++ -n framefunc -o framefunc_tolua.cpp -H framefunc_tolua.h framefunc.pkg
toluaが出力したcppに追記する。
// framefunc_tolua.cppの最後の方 /* Open function */ TOLUA_API int tolua_framefunc_open (lua_State* tolua_S) { tolua_open(tolua_S); tolua_reg_types(tolua_S); tolua_module(tolua_S,NULL,0); tolua_beginmodule(tolua_S,NULL); tolua_module(tolua_S,"c",0); tolua_beginmodule(tolua_S,"c"); #ifdef __cplusplus tolua_cclass(tolua_S,"FrameFunc","FrameFunc","",tolua_collect_FrameFunc); #else tolua_cclass(tolua_S,"FrameFunc","FrameFunc","",NULL); #endif tolua_beginmodule(tolua_S,"FrameFunc"); tolua_function(tolua_S,"new",tolua_framefunc_c_FrameFunc_new00); tolua_function(tolua_S,"new_local",tolua_framefunc_c_FrameFunc_new00_local); tolua_function(tolua_S,".call",tolua_framefunc_c_FrameFunc_new00_local); tolua_function(tolua_S,"onFrame",tolua_framefunc_c_FrameFunc_onFrame00); // 追記した // これをpkgファイルに書くことができるとよいのだが tolua_function(tolua_S,"setOnFrame",FrameFunc::setOnFrame); tolua_endmodule(tolua_S); tolua_endmodule(tolua_S); tolua_endmodule(tolua_S); return 1; }
main.cpp
#include <stdlib.h> #include <iostream> #include <lua.hpp> // lua.hだとリンク時にエラーになる #include <lauxlib.h> #include <lualib.h> #include <tolua++.h> #include "framefunc_tolua.h" lua_State *L=0; void finalize() { std::cout << "finalize..." << std::endl; lua_close(L); } void error(lua_State *L) { fprintf(stderr, "%s\n", lua_tostring(L, -1)); } int main(int argc, char **argv) { if(argc<2){ return 1; } // initialize L=luaL_newstate(); atexit(finalize); // setup luaL_openlibs(L); tolua_framefunc_open(L); // Luaスクリプト(チャンク) // をコンパイルしてスタックのトップに関数として積む if(luaL_loadfile(L, argv[1])){ error(L); return 2; } // スタックトップの関数を保護モード(エラーが発生したら // エラーコードを返す)で実行する。 if(lua_pcall(L, 0, 0, 0)){ error(L); return 3; } return 0; }
以上のソースをビルドして
> main.exe test.lua
とすれば動作が確認できる。
追記。ましな方法
手動で出力コードを編集していた部分を自動化する方法。
tolua++には-Lオプションなるものがありこれにluaでコールバック関数を記述することができる。
http://www.codenix.com/~tolua/tolua++.html#customizing
今回の例だとこんな感じで目的を達成できる。
--tolua_hook.lua -- FrameFuncに -- tolua_function(tolua_S,"setOnFrame",FrameFunc::setOnFrame); -- を追加する function process(p) if p.name=="FrameFunc" then f={ lname='setOnFrame', cname='FrameFunc::setOnFrame', } setmetatable(f, { __index=classFunction}) table.insert(p, f) return end local i=1 while p[i] do process(p[i]) i=i+1 end end function pre_register_hook(p) process(p) end
tolua実行
> tolua++ -n framefunc -o framefunc_tolua.cpp -H framefunc_tolua.h -L tolua_hook.lua framefunc.pkg
toluaを改造しようと思ってソースを見ていたらできてしまった。
これで任意のint (*)(Lua_State *L)形式の関数を後付で登録できる。