luaのlocal functionをCから呼び出す

って書くと普通に関数名を積んでgettableしてからpcallなりすればいいじゃんて思われそうですが、local関数だとC側からlocalのスコープを特定できないので呼び出せないわけですよ。
他のluaファイルにも同じ関数名とかあるだろうし。


ということでpcallできないのですごくこまる。
そもそもCから呼び出す場合も関数名を知らないといけないのでちょちょいと作った適当な名前の関数等を登録するとか無名関数を登録するとか不可能なのですが皆さんどうやって解決してるんでしょうか。


そもそもCで作った関数の引数としてfunctionを渡した場合にスタックでlua_toXXXXすることができないので取り出せないんです。
lua_tocfunctionてのがあるんですが、それはCから登録した関数ポインタの値であってluaの内部で生成した関数じゃないんですよね。
一応isfunction,iscfunctionってので関数かどうかを調べることは可能みたいなんですけど調べられてもねぇといった感じだ。

  • 欲しいAPI
    • lua_tofunction (lua_tocfunctionではない)
    • lua_pushfunction


でこういう問題ってのは大抵似たようなことを考えてる人がいるものなのでgoogleしたわけですが
http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=lua_tofunction&num=50
たったの2件ww


そのうちの一個がまさに同じことをしたいのをFAQしてて超参考になるかと思いきや
http://www.icynorth.com/forums/posting.php?mode=quote&p=658&sid=4aed4bdb7e231dd64e662178e30ec259


答えてる方が文章でしか説明してないので、実装するのにかなり手間取った。
C側

class C_Class { 
    C_Class(lua_State* L) { 
        // lua_func=lua_tofunction(L,1); 
    } 
    
    int CallLuaFunction(lua_State* L) { 
        // lua_pushfunction(L,lua_func); 
        if(lua_pcall(L,0,0,0)); 
    } 
    
    // lua_Function lua_func; 
}; 


Lua

c = C_Class(function() 
    print("Hello") 
end) 

c:CallLuaFunction() 


これを実現したいわけだ。

duplicate the anonymous function on the stack 
create a new lua_thread from the state 
move the anonymous funciton to the new thread state 
store the thread state in the original lua state as a table key in the registry with a simple bool or number as the value to prevent it from being garbage collected 


最後のcallする方法がいまいちわからなかったのだけれども「new lua_thread」するので
これはcoroutineのソースが参考になりそうだなぁと閃いたわけ。


結論から言うとlua_resumeで呼び出せたのですが呼ぶタイミングによってはよく分からない死に方するのと、関数を取り出すたびにnew threadしてしまうのでメモリがどんどん溜まっていくのと、
GCを避けるためにGLOBALSINDEXに登録するので徐々に重くなっていくのであきらめかけている。


それに同じ関数を2重に登録しなくてもいいように出来ないかと色々調べたけどどうも無理っぽいので要はグダグダです。
開放周りでもバグって死ぬしね。。。
多分、最後の "lua state as a table key in the registry with a simple bool or number as the value"ってとこの実装が間違ってるくさいのとlua_callにしてないっぽいのだけど詳しくは不明w


まぁそれでも何かの参考になると思うので一応ソースを公開します。
もうちょっと安定してると思われるものに修正(21:26)


変なタイミングで死ぬことはなくなったっぽいぞ!(22:44)
でもlua_newthreadでバリバリメモリが確保されてるっぽいので、

のどっちかを実装しないとダメかもね。


関数ポインタなんて1インスタンスでいいから前者かな。それでも一応開放もじっそうしないとなぁ。。

lua_State *lua_tofunction( lua_State *L, int index )
{
	const int n = lua_gettop( L );
	if ( n <= index ) return 0;
	int isfunc = lua_isfunction( L, index );
	
	// 関数をスタックに積む(すでに引数を渡すことで積んである)
	// duplicate the anonymous function on the stack 
	
	// new_threadを作成する.
	// create a new lua_thread from the state 
	lua_State *NL = lua_newthread(L);

	// 関数を新しいthreadのトップに移動する.
	// move the anonymous funciton to the new thread state 
	lua_pushvalue( L, index );
	lua_xmove( L, NL, 2 );

	// オリジナルのthread_stateを単純なbool値もしくは数値として登録し、
	// テーブルのキーとしてストアすることでガベコレされるのを防ぐ.
	// store the thread state in the original lua state as a table key 
	// in the registry with 
	// a simple bool or number as the value to prevent it from being 
	// garbage collected. 
	lua_pushvalue( NL, 1 );
	lua_pushnumber( NL, 1 );
	lua_settable( NL, LUA_GLOBALSINDEX );

	return NL;
}

void lua_pushfunction( lua_State *function )
{

	// 関数を呼ぶときは、ただそのthreadのスタック上に複製して呼ぶだけでよい.
	// Then to call the anonymous func,
	//just duplicate it on the stack in the thread and then call.
	// xmoveで2つ移動したので2番目に移動している.
	lua_pushvalue( function, 2 );
}

void lua_releasefunction( lua_State *function )
{
	lua_pushvalue( function, 1 );
	lua_pushnil( function );
	lua_settable( function, LUA_GLOBALSINDEX );
}


呼び出すときは

lua_State *function lua_tofunction( L, 1 );
 ・
 ・
 ・
lua_pushfunction( function );
lua_pushnumber( function, 1 );
lua_pushstring( function, "test" );
lua_call( function, 2, 0 );

こんな感じ。



開放するときはreleasefunctionを呼ぶ。

lua_releasefunction( function );


開放できた!


イイネ!


でも1インスタンス化は同じ関数かどうか判別する方法が無いのであきらめた。
だからちゃんと開放しようネ!