All Stories

插件支持国际化

  宿主程序提供了界面国际化,那么插件不能提供国际化就说不过去了,至少得有这样的机制以供支撑该种需求嘛。  得益于wxWidgets对国际化的良好支持,要让嵌入的Lua解释执行的Lua脚本也能根据宿主程序的本地化信息进行正确的处理非常简单。wxWidgets中对要进行国际化的字符串用_()进行包裹,其实这是一个宏,用于调用真正的翻译功能,比如wxGetTranslation。所以在嵌入的Lua中将wxGetTranslation函数注册到Lua中即可,然后在Lua脚本的最开始处将该函数换个更简单的名字,比如_,这是最好的名字了,哈哈。这样就可以在Lua脚本中对需要进行国际化的字符串也用_()进行包裹,它会调用wxGetTranslation函数。  GNU的国际化方案套餐中,提供一个叫xgettext的工具,可以从众多编程语言的源代码文件中提取出字符串,生成po文件以供翻译生成mo文件。比较不幸的是,xgettext不能支持Lua语言,同时由于我这个项目中使用的插件描述信息中有一部分界面信息是在xml文件中的,这也是一种自定义的格式,所在很不幸地xgettext更是不能处理啊!所以我在想,我是否要先写个可以支持Lua和我这种xml格式的类似xgettext的工具呢?Poedit太扯蛋了,居然只能认它自己生成的那种po格式的文件,稍微改一点就报错了!

AST, AST, AST...

  上午花了几个小时修改了Auto Completion功能后,用起来的感觉已经超过LuaForWindows中带的SciTE了。于是又开始寻找起可以分析Lua代码并生成AST的东西,google了大半天,发现了几个Lua项目,一个ANTLR的文本,最后都可耻地放弃了!因为那些Lua项目全都由于这样那样原因而不可用,而ANTLR,呃,我试了下,它是用LL分析的,语法元素的分析顺序不能直接为我所用,而且我在之前一点都不了解ANTLR这个东西,我甚至翻遍网络,找不到怎么让它在需要的时候记录下行号!后来又翻了一下Lua的源代码,呃,说实话真没习惯它几乎每次函数调用都有一个回调函数的用法,嗯,我真的只会一点C++了,放弃!  真是个头痛的问题啊!我觉得我还是退回去,老老实实地再温习一下编译原理,用flex和bison自己写一个分析模块吧,毕竟Lua的语言核心真的很小很小!

又有内存泄漏

  昨天晚上,突然发现又有内存泄漏了,我真的要疯掉了。一段代码一段代码地注释掉,来查看到底是哪段代码引起泄漏的,中间又偶然发现有些情况下又没有泄漏,真是太容易迷惑人了。最后发现还是原来那段扫描文件目录并分析xml文件那个函数引起的泄漏,费了老大的劲儿。这是一种比较奇怪的现象,我到现在也没有想明白怎么会有这种情况。本来以字符串作为key插入到std::map中时,如果有重复的key,我会用clock()生成一个数字添加到key的末尾,作为一个新key,以为这样就可以保证都插入进去了。现在发现似乎还是有漏网之鱼,真是太奇怪了。不过我也不想深究这个问题了,索性遇到这种重复的情况,直接在原来的key后面添加一串比较随机的字符,并再添加一个递增的编号,这样再要有重复,我就去买彩票了。  昨天在实现Auto Completion功能,呃,只是Lua编辑时可用。后来发现LuaForWindows中带的SciTE的一个做法可以学一下,就是可以分析出当前编辑器内的所有单词,以这些单词作为Auto Completion的列表源。于是今天想了想,这部分提取出单词的功能应该由C/C++来实现,Boost中有个Tokenizer库,刚好用来作这个事情。看了一下文档,用它自带的一个示例修改了一下测试一番,提取一个828KB的cpp文件中的所有单词,然后用STL的sort算法排序,再unique和erase一把,把重复项去除,最后再用STL中的copy算法复制到另一个vector中,总共这些操作,在VC2008中进行测试。最后发现,在Debug模式中大约在10700ms左右,Release模式则在500ms左右,近20倍的性能差异!我不知道这个比例是线性的,还是指数级的,反正证明一件事,那就是Release的确实比Debug的快很多!

当前项目进展及小结

  到昨天为止,基本完成Code Snippet的框架,剩下的都是些体力活。该特性要求在点击菜单项时,根据当前光标所在位置的字符串,替换成对应的代码片段。由于菜单项是通过插件添加实现的,而且Code Snippet又根据当前编辑的源代码对应的编程语言不同,也会有不同的处理,所以也是通过不同的插件实现的,这就要求插件可以再次调用插件。好在当初设计插件扩展框架时,已经考虑到这一点,所以虽然有实现过程中有需要慢慢调试的地方,但没有特别大的障碍。  完成Code Snippet后,应该开始Auto Completion特性的开发。该特性是本项目中可算是难度最高的特性之一,同时又有比较高的准确性和运行效率等要求。还有点比较头痛的是,针对不同的编程语言,可复用的东西不多。  此外,还有个功能应该尽早加入,就是处理文件的不同编码。比如通常,尤其是早期的代码,都是直接使用ANSI编码保存。而现在已经比较常用的是保存成UTF-8等编码方式,特别是像LaTeX的一些处理器直接要求输入文件是UTF-8编码。所以应该能在文件的装入和保存时,可以自动处理文件的编码问题,这可以通过iconv或ICU实现,不过问题就在于有了选择,才是苦恼啊!目前我倾向于使用iconv,因为相比之下更轻量,而且够用。  这些天发现,Lua的字符串连接符..效率还真低,怪不得Lua要提供*all和table.concat等设施。  本来Lua中操作XML有几种不同的选择,这跟在C++中情况差不多,我选择的是比较轻量的ltxml。而昨天在Code Snippet特性的开发过程中发现,我把所有的信息都保存在xml中,而每次完成snippet时都从xml中读取,开始几次还是正常的,但只要过一会儿,在调用xml.open时就会报什么试图index一个function值中一个number值,还真是诡异,但调试发现这时无论xml.open还是传入的参数都是正确的,很是纳闷啊。于是只好规避一下,只在开始时读一次,全部都装入到内存中,以后就直接读内存了。  一直以来,都是通过print来进行Lua脚本调试,真是应了那句“一夜回到解放前”。前两天才在界面上加了一个专门的输出窗口用于从Lua脚本打印字符串过来。好比是当年写Windows GUI程序时,用MessageBox调试进化到用OutputDebugStrng进行调试。昨天记起来有LuaLogging这么个第三方库,于是仔细看了看,很简单的功能,只有几个lua文件,可以记录日志到文件、控制台、socket、email或数据库中。于是我参照这些appender的实现,加了一个新的appender,用于将日志打印到宿主的插件输出窗口中,感觉不错。  接着是使用luabind的问题。在网上看到有人说luabind的各个版本都存在指针的double deleting问题,这让我有点惶恐。好在今天看邮件列表时,看到luabind的作者说,这种问题只出现在使用智能指针或类继承时切片的情况,而且要求是Lua的state先于这些对象被销毁,现在没有好的办法来修正这个问题。我想了想,这几种情况我现在都不会遇到,我只有最最简单的嵌入和扩展交互。之后,又发现有人在邮件列表中写了一个luabind和SWIG的性能比较,SWIG的封装比luabind的快一倍。看到这个结果,我觉得是意料之中,SWIG的封装方式比较底层,调用快也是正常的。不过luabind的作者说,他写了些benchmark的测试,在未发布的luabind 0.9中已经有不小的改进,虽然仍然不比SWIG快,但相比0.8.1版本,差距缩小了约一半,期待0.9的发布。  昨天无意中看到云风blog上一篇老文章提到有Lua Ring这么个库,可以在Lua代码中再创建个新的Lua state,让某些代码在这个新的state中运行,从而保护比较重要的核心state。我潜意识中认为,像我现在这个项目使用嵌入Lua来作为插件扩展的运行环境,确实要用一个比较安全的环境,即所谓的沙盒,但这个Lua Ring怎么应用上去,以及能有多少效果,仍然有待考察。  前些天,从SVN上更新的了wxPropertyGrid的代码后,发现用GCC编译不过了,直到昨天仍然不行,实在忍无可忍,真要骂娘了。上它的sf项目见面看了一下,自11月25日更新代码后,估计作者就压根没发现这个问题,于是在上面提了个单。今天发现作者已经回复那个单,并在svn trunk中已经修正了该问题,总算松了口气。  今天仔细学了一下如何让wxWidgets支持国际化,发现非常简单。只要在程序初始化时,自己创建一个wxLocale对象,把mo文件的搜索路径加进去,设置好当前要使用的语言。其他想要被翻译的字符串用_()宏替换wxT()和_T(),如果是个wxString对象,就用wxGetTranslation(),这样在这些字符串会自动从mo文件中读出相应的翻译后的文本,感觉比ini等配置文件,或是国际化资源dll的方案方便很多。不过为了让插件支持国际化,也可以使用类似的方案,但是我有poEdit时发现它只能从源代码中提取出需要翻译的字符串,不能全新的创建一个,这太土了,以后一定要自己写个好用的。  最后的一个问题是,现在的插件扩展机制,容易出现重复代码,比如相同的功能会在菜单中写一遍,在工具栏中也写一遍,这该怎么修改一下呢,呃,得仔细考虑考虑

找到关键跳,再强的加密算法也没用

  突然想研究一下软件注册机制中使用比较强的加密算法,能带来什么效果。说到比较强的加密算法,用非对称的算法应该是公认的比较好的选择。流行的大概有RSA、ElGamal和ECC了。翻出几本买了好久,都没仔细看过的书,最后发现我的数学基础实在很差,这方面的思维分析能力也很差,最终大致能看懂原理的只有RSA算法。RSA算法的强度建立在大素数因子分解的基础上,只要选择足够大的两个大素数,当然还要受制到当前主流硬件运算能力,就能保证一定意义上的安全。  目前有很多开源的加密算法库和大数运算库,可以提供RSA算法的实现,比如MIRACL、Crypto++、LibTomCrypt等等。结果发现MIRACL只能免费用于非商业用途,而LibTomCrypt的接口让我比较迷惑而不会用,剩下的Crypto++倒是可以正常使用,接口设计得也非常方便易用,缺点是体积大了点,而且很早以前在网上看到过有人说它有些算法可能涉及到一些版权、专利的问题。  最后是使用RSA实现软件注册算法的问题,从书上看到,注册机使用RSA私钥对用户名进行解密,解密的结果则作为注册码,用户拿到该注册码,输入到软件注册界面后,软件使用RSA公钥对注册码加密,能得到用户名?不过我发现,在用Crypto++进行计算时发现,随便取的一个字符串作为密文用私钥根本解密不了啊,会抛异常说无效的密文,其次,使用公钥进行加密时,也有限制,对明文的长度有限制,根本不能对很长的,可能是密文的字符串进行加密呀!倒是用RSA进行签名是可以的,这种签名方式就与MD5、SHA之类的单向散列算法效果类似了,只能确认明文是否被修改过。  不过我到PEDIY的论坛上问了问,其实采用多强的加密算法没多少区别,只要破解者能找到最后判断的语句,找到关键的那条跳转语句,就可以爆破了,前面的那些复杂的加密解密运算全做无用功了。汗!

库使用注意

  上午整了一两个小时,在wxWidgets程序中使用第三方库wxPropGrid,结果发现在VC2008中链接时有几个warning,虽然看起来刺眼,但似乎是可以正常运行的,也没有很在意。然后用MinGW编译链接,最后链接不通过,报未定义的符号,而这些符号是之前用VC2008时报warning的那几个,这就说明不是库编译得有问题,就是本身程序编译得有问题。  我先把焦点放在库上,wxPropGrid是编译成静态库的,这不但编译链接选项不同,连有个宏定义(静态库是WXMAKINGLIB_PROPGRID,动态库是WXMAKINGDLL_PROPGRID)都不同。我仔细观察了该宏定义对源代码的影响,并参考了wxScintilla的做法,发现区别很小,基本可以忽略。于是我琢磨着如果实在不行了,把wxPropGrid编译成动态库试试。正在这么打算的时候,突然想起来,这个宏定义在主程序中没定义啊!一定是这个原因!于是修改了主程序的配置,加上了这个宏定义,重新编译,发现果然有效,VC中也不报warning了,MinGW中也可以链接通过了!  其实这是个老问题了,只是平时很少遇到这种情形,一时没想起来。

修正3处内存泄漏

  不知从什么时候起,程序退出时,VC的输出窗口中就会打印一大片内存泄漏信息。开始还没怎么在意,认为只要程序功能正常,有点儿内存泄漏实在不是什么大不了的事情。最近随着代码的增长,似乎打印出来的内存泄漏数量也随着增长了,我仍然有些想逃避,安慰自己说不定是wxWidgets的问题,其实用膝盖想都知道,这种可能性太小啦!  晚上实在忍不住,或许真是只是想证明,确实是wxWidgets的问题吧,决定看一下到底是哪里泄漏的。因为程序使用嵌入Lua,很多功能都通过脚本扩展实现了,C++的代码量不多,基本上只剩下一些主框架界面的创建和消息响应转发的工作,所以没花多少时间,通过分段屏蔽代码来检测内存泄漏的源头。  最终发现有3处,而且确实都是我自己的代码有问题。  首先是有个singleton在程序退出前没有销毁掉,好像在GoF中还是《Modern C++ Design》中说的,这种情况的singleton销不销毁影响不大,不过用Loki中的Singleton是不会有这种问题的,所以还是为了美观起见,主动销毁吧。  然后,稍稍花了点时间,发现有一处new了一个对象后把指针插入到std::map中,却发现new出的对象个数最后比map中的元素个数多几个,那么最后通过迭代map销毁这些对象时,就有几个漏掉没销毁掉了。我一时间还没想明白怎么会出现这种问题的,后来想起来,有几个对象插入时估计是使用了相同的key,于是只能在map中留下一个。程序运行表现倒是没错,这是一种奇怪的现象,但在这里是合乎逻辑的,但仍然要改掉。  最后,发现是new了wxFlatNotebook后,就有泄漏。照理说,这种有父窗口的子窗口对象创建后,wxWidgets是会负责销毁的。所以估计是wxFlatNotebook有什么特殊的要求,从sourceforge上找到它的页面,有SVN下载选项,最近一次更新是2008年的事了,估计是不会更新了。它的作者居然就是CodeLite的作者,但我发现似乎CodeLite本身就没用这个组件,作者又自己实现了一套标签系统,不过都放在CodeLite里没独立出来。对比了一下我使用的wxFlatNotebook的版本,应该是2.1版,跟CodeLite代码里放的那份是一样的,SVN trunk中的至少是2.2以后版本了。于是下载下来,还有sample,这才是最重要的,看了一下sample在主窗体的析构函数中调用了一下wxFNBRendererST::Free()。把这条语句加到我的程序中,真的没有泄漏了!  现在舒服了,不报内存泄漏了,能这么顺利地解决3处内存泄漏问题,跟程序架构有很大关系啊!

渐渐习惯用Lua写代码了

  今天本来打算把那几个菜单项的功能完成的,不过后来又转去做其他事情了,计划执行力不强啊,唉!  要在Lua中操作xml,在现在的软件开发中,这是个很常见的需求,xml已经变得无处不在了。在神作PIL中提到的是用expat库,呃,我只会用DOM,所以只好找其他的库了。前些天在luaforge上看到一个叫ltxml的,才0.2版本之后就没更新过了,用的是TinyXPath和TinyXML,我决定好好考察一下。  它没有文档,只有一个readme后面四五行C代码示例,不过这足够了,看一下那个cpp文件中注册的方法,基本可以猜出用法。总的说来,先require,再用xml.open方法打开xml文件,然后就可以用TiXMLDocument的select等方法得到TiXMLNode,就跟我熟悉的用C++操作DOM的做法一样了。  说起来TinyXML的表现不差,基本能满足我当前的需要,我有点儿后悔当年花了那么大力气将Xerces C用VC2008编译了一遍,又绞尽脑汁用MinGW编译了一遍,还自己封装了一把,以适应STL中的算法。对于我来说,它太庞大了,让我畏惧,那么大一个却仍然要让Xalan来处理XSLT和XPath。同样,libxml和libxslt给我的印象也差不多,它们甚至没能让我顺利编译!  昨天说到的,现在程序崩溃总是无声无息地自动退出了。今天想了想,其实之前好像也想到过,会不会是因为LuaJIT的缘故,于是换了官方的Lua的dll来用,果然在原本会引起退出的地方,Lua只是而压了条错误信息到栈中,LuaJIT的行为没跟Lua一致啊!在Lua list上发了个邮件问问,结果一个老表说他没能重现,问我有没有证据,我汗,用Wink录了个8MB的操作录像,然后发现这maillist限制最大附件是40KB,严重超标,还要等人审查。总结一下崩溃的条件,在Lua代码中调用第三方C/C++代码注册的类和方法,如果方法、成员不存在,就会退出。而Lua是能处理成将其识别为一个nil,然后报不能在nil上进行函数调用之类的话。  总之,一切在向好的方向发展,甚至自己已经能渐渐地习惯于写Lua代码了,不像之前那样非C/C++不爽!

狂烈地崩溃

  昨天偶然发现一个超级严重的问题,程序运行一小会儿就会自动退出,什么提示都没有。至于没提示,这已经有一段时间了,照理说,内部状态、逻辑不正常么,可以给个Windows的崩溃报告嘛,可是它偏偏没有,弄得我要跟着崩溃了。  后来在代码中加入一些跟踪语句,发现出错的原因跟我的猜测一致,内嵌的Lua解释器栈溢出了。这是个很头痛的问题,以前听人说过,如果没有sandbox,插件运行环境是不可靠的,呃,最出名的是chrome的架构,经典的sandbox。但是我这个程序跟它的情况有点不同,在主窗口和子窗口上都有大量的用户交互操作,以及主窗口和子窗口之间大量的交互,子进程间的通信会很复杂。而且现在引起崩溃的,都是主窗口中的逻辑,所以还是会导致整个程序的不可用。  昨天晚上调试了好久,发现只要更新工具栏按钮或主菜单项的界面状态的响应函数打开后,过一会儿就会退出。所以最后可以把范围缩小在C++调用Lua函数的那一块代码上。我不怀疑Lua的代码有问题,凭我现在对Lua的了解,即使真有问题,估计我也是束手无策的。既然是栈溢出,而且时间不长就可以重现。我仔细地看了那块代码,又看了Lua manual和PIL,以及Luabind Documentation,发现我一直忽略的一个问题,在调用Lua的C API出错后,Lua经常会把出错信息压入栈中,而Luabind可能会直接将其封装为luabind::error类型的异常抛出,然后我就只是看一下那个字符串内容,却没其他处理了。这是一处错误,应该在提取字符串后,将其弹出。另一处错误是,我这里调用Lua中的函数,都是存放在一个表中的,所以中间无论哪个步骤出错,都应该把先前压入栈中的东西弹出。还有一处错误是,最后我从Lua栈中获取到函数后,用luabind::object封装了一把,然后luabind::call_function来调用,这时我又直接返回了,却没把这个放在栈中的函数弹出。  昨晚解决了这三个问题后,还以为所有问题都已经修正了。今天又测试了一遍,发现过了约半个小时后,程序还是自动退出了,而且连那exe文件都没了!我要疯了!  唉,这什么都是从零开始的,风险实在太大了。使用wxWidgets是第一次,复杂的内嵌Lua扩展框架是第一次,使用Luabind是第一次,使用wxLua是第一次,把所有东西混在一起用更是第一次!而且很不爽的是,已经用惯了MS的解决方案的我,没有像MSDN这样的大而全的文档极不适应,那些说使用开源的东西成本低的人,不知是真的短视,还是别有用心呢。