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 […]

关于从DLL导出模板函数

模板在C++中本来就是个比较复杂的领域,而当它和DLL结合到一起时,事情变得更加有点复杂而有趣,最近就遇到了这样一个问题。 我有一个用于生成单件的类模板,它声明在一个DLL项目中,如下。 我试图导出GetSingleton()函数,因为我希望在其他导入该DLL的模块中能够通过调用这个函数而得到单例。基于该Singleton类模板定义一个单件的类很简单,只需要继承实例化的类即可,比如我在DLL内部定义了两个单件类,LogManager和ResourceManager,他们的定义看起来像这样。 OK,DLL编译通过。然后,我在另一个项目模块中导入该DLL,我需要在这个模块中使用LogManager类和ResourceManager类,由于它们都是单件,正如前面所说的,我通过GetSingleton()函数得到单例,以访问它们的其它接口。 然而,状况出现了,build该模块时出现了链接错误:unresolved external symbol “public: static class ResourceManager & __cdecl Singleton::GetSingleton(void)”” ,提示死活链接不到ResourceManager的GetSingleton()函数,然而同样是使用Singleton模板,LogManager的GetSingelton()函数却是链接成功的。 如果足够了解template的机制,就很容易找到问题所在。问题的原因是,除非你显示的实例化(或者调用)了某个模板函数,否则这个模板函数在编译生成代码中(.obj)是根本不存在的。所以,尽管我们为模板函数打上了export的标签,但其实什么也没有导出。而LogManager能链接成功,是因为它在DLL中(ResourceManager中)有使用,从而编译器生成了LogManager::GetSingleton()并导出。 所以,为了能够同样也导出ResourceManager::GetSingleton(),我们必须在DLL中提前显示的实例化它(explicit instantiation)。 LogManager和ResourceManager都是在DLL内部定义的单件类,那能不能在DLL外部也基于Singleton模板快速定义单件类呢?可以,不过GetSingleton这个函数对于其它模块就是隐形的了(根本不存在),你只能在每一个单件类中重新定义一个GetSingleton函数,以避免链接错误。 这个问题有没有完美的解决方案呢,即我们能否让一个模板函数完全导出,使得在别的模块中也可以自由的基于任意类型实例化函数呢? 去google上试图寻求答案,无果。不过说这个问题还真不少,比如CodeProject上的这篇文章:http://www.codeproject.com/KB/cpp/MemberTemplateDLL.aspx 。它提出可以使用inline的方式来解决这个问题,但不保证结果,因为本来编译器对于inline的处理就是个未知的行为。他同时还提出,这可能是一个跟编译器相关的问题,因为对于模板的处理是个跟编译器密切相关的行为。该作者在VC6.0上遇到了这个问题,我的是在VS2005(VC8.0)上,在其他不同平台或版本的编译器上会怎样我也没有测试过。 总之,记住很重要的一点,当导出一个模板函数时,需要格外的警惕。在MSVC编译器上,只有在DLL中被显示的实例化的版本,相应的函数才会被导出。

Posted in: C++ by Benny Chen No Comments

小心动态链接库链接静态库时的内存错误

最近写的模块,在独立的应用程序中测试是没问题的,但把它装配成DLL后,再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的: module就是我所写的模块,在这里被封装为DLL,因为要使用json相关的功能,该DLL链接了一个静态库 (jsoncpp.lib)。最后在应用程序中导入并使用module.dll,同时因为在应用程序中也需要用到json,所以应用程序也链接了jsoncpp.lib。 以下用一些伪代码来描述这些模块间的调用关系,以具现出这个错误。 jsoncpp.lib为c++提供了功能齐全的json操作,其核心的类是Json::Value。(阅读本篇文章你无需了解太多json) module.dll中导出了一个接口: 应用程序: 在Debug模式下,当main函数执行完毕,对Json::Value root进行析构时,程序便出现了异常。分析下,很显然,调用ModuleClass::MallocMemoryHere时申请的内存,是在module.dll中申请的,而对这些内存的析构则是在应用程序(.exe)中进行的(析构root会同时析构append在root上的所有子Json::Value)。不过,这是异常的真正原因么? 追踪到异常的出错点:dbgheap.c文件中那句ASSERT语句。 注释中的最后一句话”The pointer MUST come from the ‘local’ heap“引起了我的警惕,难道对于内存的申请和释放不是在同一个heap上,除了‘local’ heap还有一个什么heap么。 去MSDN上搜索了关于_CrtIsValidHeapPointer,似乎找到了答案,以下这段话是MSDN上对于_CrtIsValidHeapPointer的介绍: The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time […]

Posted in: C++ by Benny Chen No Comments , ,

Static Initialization Order Fiasco

Static Initialization Order Fiasco (SIOF),我也是最近才知道了这个说法,因为在开发程序的时候被它bug了:对于一个static变量,不管它是全局的或者是类的成员变量,访问它的时候不一定总是成功的,甚至会造成程序crash,因为不能保证它在被访问时已经被初始化了(跟初始化的顺序有关,所以称为初始化顺序的Fiasco)。以下将制造一个非常简单的SIOF情形: Whatever.h Whatever.cpp 一个简单的类,Whatever,包含几个static成员变量,然后在构造函数和析构函数中都分别打印这些静态变量的值,乍一看似乎没什么问题,但却有潜在的SIOF的风险。我们容易默认为在调用Whatever的构造函数的时候,Whatever空间中的static的成员变量已经被初始化了,其实不然,现在制造一个SIOF引起crash的情形: 因为g_whatever是global变量,所以最先被初始化,在调用Whatever的构造函数的时候,Whatever空间的静态成员变量还未被初始化,所以访问这些静态变量肯定出错。在VS的编译器下测试的结果: Construct Whatever static int:5 static string: (调用m_vec.front()导致程序crash) 奇怪的是对于int这种built-in的类型却能得到正确的值,不知编译器在背后都做了哪些手脚,猜想可能是在程序编译的时候他们就被值替换了。而string和vector应该都属于自定义类型(初始化需要调用构造函数),未初始化之前访问肯定是错误的,所以打印出的string是个空值,而访问一个空的vector的front元素则直接造成程序crash。 根据初始化和释放的对称关系,所以在析构函数中访问这些静态变量同样也是失败的,因为在析构g_whatever的时候,Whatever空间的静态变量已经被解决掉了。 SIOF是非常难于检测的问题,这个例子是一种最简单的情形,在我的项目中,我并没有定义什么global的成员,但是因为使用了很多前置声明(forward declaration),还有一些Singleton,造成了一个非常隐蔽的SIOF,花了很大的力气才找到,痛苦的过程。 要解决SIOF问题,需要用一个function来包装static变量,即利用函数内static变量的construct-on-first-use特性。 修改后的Whatever.h 用GetStaticVector来包装之前所需要的静态的vector,就能保证在调用的时候,它一定已经被初始化了。再次运行之前的测试程序,OK了。 总之,我们对于static变量的使用要保持一颗警惕的心,如果不确定在使用时它是否已经被初始化,就要使用函数包装static变量来防止Static Initialization Order FIASCO! Fiasco, what a cool word.

让管理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库,也没有提供任何智能指针,所以使用禁止复制来保证安全。

用Shell Script导出SVN版本间变更的文件

另外记录一条批量替换字串的命令:

Posted in: Linux, Shell, SVN by Wang Xiaoxing 2 Comments , , , , , ,

Javascript Beautifier & JSLint Integration with Editplus

This is for those who are using a IDE without js format feature by default like Editplus. First, I want to share you a javascript beautifier: http://jsbeautifier.org/, where you can paste your js code and then beautify with some customizable options. Also the format function is provided by various languages / library on the website. […]