Lua相关问题整理(4) – 让Lua的eval函数支持赋值语句

上一篇文章提到了在Lua中实现类似于JavaScript中的eval函数,遗憾是该eval函数不支持赋值语句,原因是Lua的赋值运算符是不支持返回值。所以如果要让该eval函数也支持赋值语句,就需要一个额外的工作,让它鉴别一个语句是不是赋值语句,如果是,则return的是被赋值后变量的值。为此,我写了一个isAssignmentExpression函数,比较粗糙,不过够用了,基本思想是检测语句中第一个出现的”=“操作符,且该”=“不能在一对引号当中。

Posted in: Lua by Benny Chen 9 Comments , ,

Lua相关问题整理(3)

在注册给Lua的C函数中为Lua提供默认参数 使用luaL_optstring, luaL_optnumber, luaL_optinteger等Lua API,如下示例,函数有一个默认字符串参数,默认值为””,这样在Lua中调用whatever的时候,whatever()或者whatever( “whatever”)均可。(Oh…whatever…随便…都行…) 建立Lua字符串到C enum的映射 使用luaL_checkoption这个Lua API,它可以把从Lua传来的string转换为相应的C string array中的index,从而可以建立Lua字符串和C enum的映射。以下是个简单的示例: 首先enum PlayerType定义了一组角色类型,来自人人都爱的三国杀:-)。 接着PlayerTypeList定义了一个字符串数组,给Lua使用。注意需要保证enum和字符串数组的对应,比如PlayerTypeList[PLAYER_TYPE_KING]是“KING”,同时,PlayerTypeList必须以NULL结尾。 在定义给Lua的函数testPlayerType中,就可以用luaL_checkoption将Lua传来的字符串参数转换为相应enum的值。luaL_checkoption还支持默认参数,比如在上面例子中,将第三个参数设为“INSURGENT”,如果Lua中没有提供任何参数,则PlayerType就为与“INSURGENT”相对应的PLAYER_TYPE_INSURGENT。 以下是一组测试及结果: 在Lua中实现eval函数 众所周知,JavaScript中有一个著名的eval函数,它用于把一个字符串当作一段JS代码去执行,在Lua中没有提供类似的函数,但稍微包装下Lua的库函数loadstring即可实现,以下是代码。 这样已经可以了,不过相比于JS的eval函数,功能稍微差一些,因为它不支持赋值语句,这是Lua语言天然的原因,因为Lua的赋值运算符没有返回值,在其他语言中常见传递赋值的“i=j=1”(先赋值j=1,然后将(j=1)的返回值j赋值给i),在Lua中是不允许的。所以当eval执行的是赋值运算(比如i=1)的时候,return i=1就会出错。 下面是一些测试例子: 实现luaL_checkbool 不知道为什么Lua的API没有提供luaL_checkbook函数,不过很容易实现:

Posted in: Lua by Benny Chen No Comments , , , ,

Lua相关问题整理(2) – 如何在C中为Lua提供同步调用接口

这个问题的具体描述是——C注册给Lua一个函数,但Lua调用该C函数并不能立即获得结果(比如需要访问远程服务器获取值),如何能让Lua停止并等待,直到获取到结果后,才继续执行接下来的脚本。 举个例子,比如说有一个C函数login,我们试图通过调用该函数以执行用户的登录操作并获取验证结果。首先看下面这段C代码: 可以看到,因为该函数需要访问远程的登录服务器,在系统中一般都采取异步操作(让系统阻塞等待结果是不可接受的)。但是这样Lua开发人员调用login函数时,也只能异步等待结果,下面是Lua中处理登录操作的代码。 这段Lua代码很好理解,首先onClickLoginBtn是一个按钮事件处理函数,当用户点击了”登录“按钮后,会触发该函数。该函数首先从界面上获取用户输入的user和password,然后调用了上面C中所定义的login函数。正如前面的C代码所写的,login函数不会马上得到认证结果,所以执行后马上退出。另一个函数是onGetLoginResult,这个Lua函数需要当C系统中获取到认证结果后被回调执行,以真正执行login的后续操作。所以,我们还需要在C中添加回调的代码。 在这里,我们在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函数有些小改变: 可以看到,与前面的区别只有login函数的最后一句,调用了lua_yield后再返回,而不是return 0,这样就可以达到阻塞Lua脚本的目的。事实是,lua_yield是一个比较特殊的函数,它只能作为注册的C函数的返回值使用,否则调用失败。 当系统获得服务器端的登陆验证结果后,通过lua_resume即可恢复之前被阻塞的Lua。 上面的代码是当你的系统中只有一个Lua虚拟机的情形,如果使用了多个Lua虚拟机,事情稍微有一点点复杂,login函数还需要将当前调用的lua_State存储下来,以便lua_resume的时候,可以知道恢复的是哪一个被阻塞的虚拟机。 OK,当C中有这样的实现后,Lua程序人员将会为此而高兴,因为Lua代码变得如此简洁。 系统or平台开发人员应当尽可能的为用户考虑,正如上面第二种解决方法所追求的那样。

Lua相关问题整理(1)

(Under Linux)cannot find ‘dlsym’ ‘dlopen’ ‘dlerror’ ‘dlclose’ 需要同时链接”dl“库 (Under Linux)编译lua报错luaconf.h:275:31: error: readline/readline.h: No such file or directory 需要下载并安装GNU Readline Library PANIC: unprotected error in call to Lua API (unable to get ModuleFileName) 1: 不推荐的解决方式:将Project Properties->Configuration Properties->General下的Character Set从unicode改成multi-set; 2: 彻底的解决方式,参考此链接:http://lua-users.org/lists/lua-l/2006-06/msg00427.html 如何将Lua文本文件转化为Lua块文件(chunk file) 调用LuaAPI – lua_dump 关于lua_dump: about lua_dump: Dumps a function as a binary chunk. Receives […]

让管理lua_State的类指针安全

最近因为在公司的项目中接手了Lua脚本模块的开发,所以研究了很多关于Lua脚本的东西,秉着“多看多想多记”的原则,我时刻敦促自己要及时记录下遇到的一些问题和想法。 在Lua中,几乎所有的东西都是围绕着lua_State转,所以,一般我们都会写一个类来封装管理它,比如: 这很好,不过它不是指针安全的。试想,如果一个LuaObject对象被复制,结果将会怎样。 上面这段代码将会导致运行时crash,因为luaObject1和luaObject事实上指向了同一块lua_State,这样当luaObject1和luaObject2被析构时,lua_State会被两次lua_close,这不crash才怪呢! 事实是,当一个类包含了一个指针时,我们就需要开始变得格外谨慎,除了在构造函数和析构函数中要处理指针的初始化和清理外,我们还需要考虑深拷贝(deep copy),浅拷贝(shallow copy))的问题。如果使用编译器默认生成的拷贝构造函数,它只会浅拷贝指针,而指针所指向的内存区域不会被拷贝。就像上面一样,两个LuaObject实则共享了一个lua_State。 那该如何处理让管理LuaObject类的指针安全呢,深拷贝?厄,首先我也没有深究深度拷贝lua_State具体该如何完成,不过我猜想这可是一个复杂而重型的操作,仅仅为了带来指针安全而选择此可不是一个明智的选择。 如果你是开发LuaObject类的程序员,也许你会对使用LuaObject的程序员(或许有时使用者就是你自己)说:“你不要对它进行拷贝操作不就OK了”,但这是一种严重不负责任的行为,因为这种皮球会越踢越远的。比如,程序员A使用了你的LuaObject类,他写了一个包含LuaObject的指针的类,同样A也不考虑拷贝指针安全问题,然后A又将它的类传递给了B,B封装了A的类指针,然后传递给了C,接着如此重复,C再给D,D再给E,最后E在对类进行拷贝操作时,程序crash掉了,因为在某个最底层,他们共享了lua_State。不过因为这时候已经嵌套了这么多层,E程序员或许根本不知道也不关心什么是LuaObject(他只跟D的类打交道)。这时,要想追踪到BUG之源——万恶的不考虑指针安全的LuaObject,已经非常困难了,God bless you all. 事实是,如果你不想让你的类被复制,你就应该明确而显示的禁止它。其实lua_State的这个问题非常类似于Effective C++书中的第14个条款中所提到的问题:在资源管理类中小心copying行为。对于这个问题,书中提供了两种解决方案。第一种就是禁止复制。第二种则是对底层资源的“引用计数法”(reference count)。 第一种禁止复制很简单。(关于Uncopyable) 第二种则是使用智能指针来封装lua_State,我们可以使用boost的shared_ptr来封装lua_State,即shared_ptr m_L。如何对m_L进行初始化是另一个需要注意的问题,如果使用shared_ptr的常见初始化形式:m_L = shared_ptr( luaL_newstate() ),这样是不对的,因为在这种形式下,当lua_State的计数变为0时,shared_ptr会去调用lua_State的的析构函数,这显然是错误的,对lua_State的释放动作是lua_close而不是删除。事实上,这样编译器也无法通过,如果这么写,会报出“use of undefined type ‘lua_State’”的错误,提示lua_State是一个非完成的类型(incomplete type)。 我们应该为shared_ptr的初始化传入一个删除器(deleter)。很显然,lua_State的deleter是lua_close()函数,这样最终的代码如下。 这样再回到前面拷贝的那个例子,luaObject1和luaObject2共同引用了一个lua_State,但因为使用了shared_ptr,所以只有在lua_State的引用次数变为0时,它的deleter(这里是lua_close)才会被调用,安全了! 在我的项目中,因为没有使用boost库,也没有提供任何智能指针,所以使用禁止复制来保证安全。