这个问题的具体描述是——C注册给Lua一个函数,但Lua调用该C函数并不能立即获得结果(比如需要访问远程服务器获取值),如何能让Lua停止并等待,直到获取到结果后,才继续执行接下来的脚本。
举个例子,比如说有一个C函数login,我们试图通过调用该函数以执行用户的登录操作并获取验证结果。首先看下面这段C代码:
// C code int login( lua_State *L ) { string user = luaL_checkstring( L, 1 ); string password = luaL_checkstring( L, 2 ); // 该函数将user和password发送到服务器后立即返回, // 绝不要在此处阻塞,这将严重影响效率 sendAuthenticationInfo( user, password ); return 0; } // 注册给Lua lua_register( L, "login", login );
可以看到,因为该函数需要访问远程的登录服务器,在系统中一般都采取异步操作(让系统阻塞等待结果是不可接受的)。但是这样Lua开发人员调用login函数时,也只能异步等待结果,下面是Lua中处理登录操作的代码。
-- Lua code -- 当用户点击“登陆”按钮后,执行该函数 function onClickLoginBtn() local user = ... local password = ... login( user, password ) -- 只是发送login命令 end --当获取login结果后的回调函数 function onGetLoginResult( bPass ) if bPass == true then print( "Authentication succeeded." ) ...omitted code... else print( "Authentication failed." ) ...omitted code... end end
这段Lua代码很好理解,首先onClickLoginBtn是一个按钮事件处理函数,当用户点击了”登录“按钮后,会触发该函数。该函数首先从界面上获取用户输入的user和password,然后调用了上面C中所定义的login函数。正如前面的C代码所写的,login函数不会马上得到认证结果,所以执行后马上退出。另一个函数是onGetLoginResult,这个Lua函数需要当C系统中获取到认证结果后被回调执行,以真正执行login的后续操作。所以,我们还需要在C中添加回调的代码。
// C code // 当服务器端返回认证结果后 BOOL loginResult = ... lua_getglobal( L, "onGetLoginResult" ); lua_pushboolean( L, loginResult ); lua_call( L, 1, 0 );
在这里,我们在C中hardcode了回调onGetLoginResult的代码,这很丑陋,不过可以避免,比如可以给前面注册给Lua的login函数增加一个参数callbackFunctionName,以让Lua显式的告诉系统当获取登录结果后的回调函数名称。
再次回到本文一开始所提出的问题,尽管在系统中的异步调用不可避免,但我们希望在Lua中能够有同步机制,即Lua脚本在得到验证结果后才允许被继续执行,如何才能做到这一点呢。
Lua有一个非常棒的coroutine机制,在Lua代码中可以通过协同程序来进行多线程,可以使用coroutine.yield和coroutine.resume来对协同程序进行挂起和恢复。需要达到上面所提出的目标,只需在系统中使用与coroutine.yield和coroutine.resume相对应的Lua C API——lua_yield和lua_resume即可。如下所示,login函数有些小改变:
// C code int login( lua_State *L ) { string user = luaL_checkstring( L, 1 ); string password = luaL_checkstring( L, 2 ); sendAuthenticationInfo( user, password ); return lua_yield( L, 0 ); } // 注册给Lua lua_register( L, "login", login );
可以看到,与前面的区别只有login函数的最后一句,调用了lua_yield后再返回,而不是return 0,这样就可以达到阻塞Lua脚本的目的。事实是,lua_yield是一个比较特殊的函数,它只能作为注册的C函数的返回值使用,否则调用失败。
当系统获得服务器端的登陆验证结果后,通过lua_resume即可恢复之前被阻塞的Lua。
// C code // 当服务器端返回认证结果后 BOOL loginResult = ... lua_pushboolean( L, loginResult ); lua_resume( L, 1 );
上面的代码是当你的系统中只有一个Lua虚拟机的情形,如果使用了多个Lua虚拟机,事情稍微有一点点复杂,login函数还需要将当前调用的lua_State存储下来,以便lua_resume的时候,可以知道恢复的是哪一个被阻塞的虚拟机。
OK,当C中有这样的实现后,Lua程序人员将会为此而高兴,因为Lua代码变得如此简洁。
-- Lua code -- 当用户点击“登陆”按钮后,执行该函数 function onClickLoginBtn() local user = ... local password = ... if login( user, password ) == true then print( "Authentication succeeded." ) ...omitted code... else print( "Authentication failed." ) ...omitted code... end end
系统or平台开发人员应当尽可能的为用户考虑,正如上面第二种解决方法所追求的那样。
Recent Comments