All Stories

小心缺省参数值

  昨天本来是想完成编译构建的特性的,结果先去搞工程树视图了,发现了好些工程树视图代码中隐藏的bug。   其中最诡异的莫过于在打开一个工程文件时,读出各个xml的DOM节点,调用另一个函数处理这个DOM节点,总是发现值为nil。搞了老大半天,把代码结构也调整了不少,最后发现是该函数本是个递归函数,在递归调用时居然少写了个参数,于是Lua就主动把后面缺的参数值赋为nil了。人都差点儿崩溃了,开始抱怨Lua不稳定,其实是人不稳定啊,哈哈。这次事件让我再次觉得,缺省参数值这个特性实在是很危险,以前在C++程序中也因为这个原因引起过问题。   在定位这个问题的过程中,还偶然发现,原来Xerces中的getElementByTagName是游走整棵树的,跟我原来的设想不一样,都是MSXML用惯了的后遗症。决定以后就只用Xerces了,无论是VC还是GCC都可以用,以后换个操作系统也可以用,现在Lua中也可以用,彻底告别Xerces,至于它保存成文件时格式不好看的问题,暂时不管它。   Xerces的文档一直没仔细看过,昨天发现,XMLString::transcode返回的字符串,用完后,要用XMLString::release释放,原来我都没这么做!

修正又一处内存泄漏

  前两天偶然发现,又有内存泄漏了。昨天仍然在写Lua脚本,于是没怎么管它,今天早上起来wc时,突然觉得这内存泄漏实在是眼中钉,肉中刺,一定要解决。   首先是发现,只有在程序中打开过文件后,才会有内存泄漏,而且泄漏的数量跟打开文件的数量似乎是成正比的。于是开始查看打开文件部分的代码,发现这部分代码实在写得太简单了,几乎不存在泄漏的机会,绝大部分函数都是立马将请求转发到Lua脚本去了,我假设Lua脚本不会出现泄漏。   于是我尝试新建一个文档而不保存,发现果然没有泄漏!这就说明泄漏可能的两个来源,一个是读写文件时,一个是根据文件类型作出不同响应时。接着,尝试打开一个txt文件,也没有泄漏,这说明第一个猜测的泄漏源不成立,那么把注意力集中在第二点上。   我先是怀疑是不是lexer相关的代码引起的泄漏。于是又用程序打开cpp文件,这是一种处于中间状态的文件,程序能认出这种文件的类型,应用相应的lexer对其着色,折叠等,但不做更多的操作。果然也没有泄漏,再打开lua文件,又有泄漏。仔细回忆了一下lua文件和cpp文件的区别,大概最有可能的两处是符号视图和代码片段视图,这两个视图在打开lua文件时是会被操作的,而打开cpp文件则没有。   这时,我先去看了一下符号视图,先把刷新视图的代码屏蔽掉。编译运行,没有泄漏。已经将范围缩小到这个函数内了,再只屏蔽添加新节点的代码,发现又有泄漏了,说明泄漏不在添加新节点的代码上。通过一段一段地屏蔽代码,直到最后发现,是我自己写的Xerces-C++的封装类在从字符串中载入xml文档后,就会有泄漏,所以可以确定问题出在这个载入函数中。   打开这个函数的代码看了看,非常简单,只有3行代码,其中第1行就new了一个对象,后面却没有任何释放的动作。在后面加了一句delete,重新编译测试,确实没有泄漏了!还有一个Lua使用Xerces-C++的绑定库,也要做相应的修改。   解决,安逸!

几个Scintilla/SciTE相关项目

  Scintilla和SciTE就不多说了,我接触到它并真正用到它,有3年多了,一直跟踪着它的最新发展。发现有几个跟它相关的项目,比如有用(有趣),值得记一下。   第一个要说的是SciTE-ru。这是个SciTE的增强版,大概作者是俄罗斯人吧,所以后面加个ru。SciTE从它本身的定位出发,并不会增加很多强悍的辅助功能,只能说是Scintilla有什么能力,SciTE便有什么功能。而SciTE-ru的出现则试图从第三方增强SciTE的不足之处,并修正了一些在Scintilla或SciTE中存在的小缺陷。如果对SciTE有爱,那么SciTe-ru则会让人觉得更有爱。遗憾的是我不在此列。   接着要说的是SciTE LaTeX IDE。这个项目在SciTE-ru的基础上发展而来,但它的定位是成为一个好用的LaTeX编辑器。作者不但在SciTE在软件包中增加了一些编写LaTeX常用的小工具,还在界面上增加了一批方便的按钮和菜单,更重要的是作者修改了Scintilla官方的LaTeX和asymptote的lexer,却没有合入到官方代码库中去。照作者的说法是修改后的lexer要求太苛刻,会破坏原有的应用程序。我是个LaTeX的初级应用者,确实使用SciTE LaTeX IDE编写过几篇文章,觉得从编辑器的角度出发,已经算是很不错了,但如果定位是IDE,则还是差得远的。   最后要说的是Sicintillua。这是个很有趣的Scintilla/SciTE的fork项目,它移除了所有Scintilla中使用C++编写的lexer,取而代之的是一个叫LexLPeg.cxx的文件,里面定义了两个lexer,分别是null和llpeg。对于各种已有编程语言lex支持都是通过外部的Lua脚本实现,所以用SciLexer原有的标准接口查询和设置的lexer都是llpeg,它又增加了两个接口用于设置直正的lexer。这个fork解决了一直以来就有的Scintilla增加lexer不方便的问题,因为有一拨人觉得,使用C++编写lexer并编译进Scintilla中实在麻烦,确实我也这么觉得,只不过我可以容忍,因为我直到现在都没有自己要添加lexer的需求。我简单地看了一下Scintillua的代码与官方CVS中的代码进行了比较,发现修改的地方不是很多,不过修改了一些接口,我不太喜欢,有可能的话,我要在Scintillua的基础上再出个修改版,能更方便地跟官方代码树进行融合。

反思自己的时间管理

  以前蹲公司时,最开始是需要每天在notes上填工作日志的,几点几分到几点几分做了些什么事,涉及到哪些人。当然,没人教过我具体应该怎么填,我只是按照自己的喜好,也许是每天下班前花些时间填一下,而且还常常忘记。而恰恰这个工作日志又是需要每周让主管审核的,我就被逮到过,主管发来邮件让我补齐落下的日志,觉得很丢脸。   后来,除了要每天填notes上的工作日志外,又多了项不定期地向主管反馈工作完成进度,是用Microsoft Project管理的。主管会发来预先已经安排过的工作项目,我们则根据实际情况填入各个项目花费的时间,再通过邮件发回给主管,主管以此来跟踪下属们的工作进展。事实是,我常常不记得每项工作到底花了多少时间,所以上报的数据估计有3成到5成是不准确的。   再后来,另外有个主管自己用Excel的VBA编写了一套宏,然后放在公共服务器上,让我们每天往里面填写内容。工作内容主管已经事先在其中已经安排好了,我们需要的只是简单地写上ok或受阻等几种简单的情况。这种日子一直持续到我换部门,后来如何我就不得而知了。   到了新的部门,首先让我觉得很奇怪的是,他们居然不用填工作日志,他们认为要求填工作日志实在是件很过分的事。一开始我还有点儿不习惯,尽管不需要再向主管反馈了,自己仍然为了证明上班时间没有偷懒而继续自得其乐地填了几个月。后来实在是因为觉得没什么实际的作用也放弃了。   后来很长一段时间处于混乱的无组织状态。直到我被派去给别的部门做一个工具时,因为各方领导对该项目的重视,要求我每天反馈进度,于是在RedMine上通过一个自研的插件,只要我每天往RedMine上填写新闻,那么RedMine就会在每天午夜时分自动将该新闻作为邮件内容发送给各个关心该项目的领导们。我在新闻上写的内容可谓五花八门,每天遇到什么问题,如何定位bug,如何作出技术抉择,发布日期又要跳票了等等,觉得很开心很安逸,这才是比较适合我的方式。这个项目我一直做了一年半,然后又交给其他同事,我则被调回做其他项目。   调回后,项目组中又开始尝试敏捷和迭代开发,主管倒是做过一些努力,结果仍然是无序的。这样的无序状态我一直经历了半年多点,直到辞职。其间最常用的办法是,把最近一天或几天内要做的事一条一条写在笔记本上,然后完成一条就在上面划掉一条。事后回顾,也是很有成就感的事情。   这里牵扯到一个问题,说大了,是项目管理,说小了到个人,则是时间管理。以前看到过有人说,某老外的时间管理可以精确到一刻钟。当时是没有多少想法的,只是觉得这样细化的计划或回顾可能价值不大。   这几月来在家写代码,一直以为自己一天十几个小时都扑在电脑前了,应该产量很高吧。直到昨天偶然在网上找到一个叫Manic Time的免费小软件,可以记录下每天使用各个软件的时长。今天看了一下,才猛然发现,我这一天,花在编码上的时间竟然不到2个小时!真是触目惊心的数据啊,我实在是太堕落啦!   我需要好好反思!

Lua 5.2 is coming…

  在Lua的maillist上已经放出了5.2的第一个work version,引起一帮人热情高涨。   不过那帮人说话也真不客气。Mike Pall发难说,不要引入bit库,看不起Lua开发组的同学啊。Alexander Gladysh同学则是先后几次纷纷列出众多已实现却没有在文档中提及的变更。   初步看了一下changelist,看得我有点儿心惊肉跳,相比5.1还是有不少改变的,至少已有的代码有些是要废掉了,比如没有LUA_GLOBALSINDEX了,光这一点就让不少C扩展库和绑定库罢工。   虽说现在只是一个work verison,以后的实现会有所修改,但我对于能提供向5.1的兼容比较悲观。不过想想现在还有不少人用着5.0的,心里稍微平衡了一点儿,等我用到的那些第三方库都能提供5.2兼容后,再考虑升级吧,先继续用5.1!嗯,升级机会得利用好!

Lua调试器W.I.P.

  话说我已经怀疑起engine和controller都反应慢是由于延时轮循引起的,于是下定决心要改成通知的方式。翻了一篇Jeffrey大牛的《Windows核心编程》,发现只要用Event机制就可以实现了。不过如果直接用API来操作Event,我就有点儿不爽了,现在这个项目我比较希望它能在源代码层次尽可能地平台无关。于是又在Boost文档是找了一遍,发现thread库中似乎有对应的设施,其实这是可以理解的,本来就是线程间通信,同步的机制嘛,thread库虽说比较轻量级,但有这么个功能也是理所当然的。修改完代码,测试了一下,果然无论engine还是controller都是立马响应了,安逸呀!   昨天又去了一趟4S店,没想到买个车上个牌有这么麻烦,还要等于1月15日才能在这边上,于是只好再去领了张临时牌照,可以拖到15日了。回来后就没什么心思继续写代码了,唉,我的意志力真是薄弱地几乎没有!    昨天晚上躺在床上,突然想起来,原先定的调试器三步走,后面二三两步的顺序应该反过来。因为engine端的启动(注入)方式修改后,必然对controller端要进行一定的同步修改,为了减少这部分同步修改的工作量,就不应该先把controller端合入到宿主程序中去,目前使用控制台方式的controller修改起来应该工作量要少得多。接着又担心起controller合入到宿主程序中的方式,因为可以预见到的是界面到时候会根据网络通信线程中接收到的信息做出不同的响应,照以前的经常,如果用的是MFC之类的方案,可能就是SendMessage给界面线程就可以了,可现在用wxWidgets,不知道应该怎么处理,看来又得好好看看Code::Blocks和CodeLite的代码了。

独立后的第一篇

今天在hugege.com上买了个空间和域名,从此咱也走上了独立blog的道路。 让GFW见鬼去吧!!

Lua调试器W.I.P.

  今天终于把基于socket的远程调试架构搭建起来了!  昨天定位到controller给engine发送命令后,engine并没有收到,于是今天首先是开起两台机器,这样就可以用Wireshark抓包了。经过抓包发现,controller确实已经把命令发送过来了,只是engine没有正确接收。一番折腾后,参考了一下lldebug的代码,把engine端的代码从io_service.run()改成多次io_service.poll_one(),居然正常运行了。这怪我没有认真理解asio的用法,io_service居然有4个用于处理事件的方法,除前面那两个外,还有run_one()和poll(),从文档中我实在分不清到底它们有什么区别,以及分别适用于哪些使用场景。不过既然现在可以正常运行了,那我暂时也不去管它了。  socket的问题解决后,我就埋头于定位,修正controller与engine的交互过程。在我看来,这应该是状态机,controller端一个,engine端一个。不如我没有仔细考虑,只是因为现在的问题很简单,步骤不多,随便跑个例子试一下,有问题就解决,到现在基本上也是ok了。  编码过程中,发现boost::shared_ptr是个不错的东西,尤其适合于asio。比如要发送一段数据,把这段数据放在一个boost::shared_ptr中,无论它怎么传递,不用管它的生命周期。  自从远程调试架构完成以来,我就发现一个问题,从controller下发一个命令,engine要过一会儿才会作出响应,而engine反馈了信息回来,controller也要过一会儿才会显示出来。开始的时候我以为是网络传输延时的问题,后来把双机调试改成在同一台机器上调试,loopback应该够快了吧,结果仍然没什么变化。最后我想到,估计是我其中加入的延时操作引起的,如果在写之前发现队列为空,那么就一直等待,每隔1秒钟看一下队列中是否有可写的内容。所以要把这个设计改一下,不能用定时轮循,而要用事件通知,又得好好复习一遍《Windows核心编程》了。  接下来,就是实现各个调试命令了!

Lua调试器三步走

  估计这个Lua调试器是最近几个月来遇到的最具有技术挑战性的特性了,大概要分三步走。  首先,确定使用远程调试架构。用C++实现与用户交互的操作程序和与Lua解释器交互的执行部分。现在仍然在这个阶段。昨天差不多把socket通信的部分搭起来了,这也花了我不少精力,是用boost.asio做的,实然觉得自己对boost.asio的了解增进了很多,也突然觉得其实boost.asio似乎挺好用的。但是仍然有点小问题,比如我本来希望执行部分作为C/S结构中的客户端,它能在连接上服务端后立马写点数据给服务端,可是似乎总是没写出去,要先收到些东西再写才会成功。不过现在暂时不管这个问题,今天在写与Lua解释器交互的部分,发现代码真的是越来越多喽,一个又一个新的类被创建出来,真是面向对象惹的祸呀!没写完,中间脑袋稀里糊涂的,明天继续。  接着,把与用户交互的操作程序改写成Lua,并与宿主程序集成,可以在IDE的编辑器上就让用户进行操作和反映调试器执行结果。这里要注意的是,到底到时候要放多少代码是在宿主程序中实现的,多少代码又是让Lua脚本实现的,又有多少代码是用C++写成作为Lua的扩展库的,几部分又是如何交互的。  最后,把调试执行部分改成dll,以便注入到被调试进程中去,这样就可以调试任何嵌入了Lua解释器的程序了,只要Lua解释器是个dll即可。注入后,要hook掉几个Lua的C API,包括获取lua_State,装入文件。这里应该要注意的是,使用Lua解释器的C API不能直接调用和链接了,要动态获取API地址了,这样才能使用注入进程的那个Lua解释器的dll了,不过其实可能不需要这么复杂,只要额外说明如果要用本软件调试的话,要求用我指定的Lua解释器的dll和lib即可。我觉得这点是可以做到并且在某些情况下必须做到的,因为曾经还听人说过,很多内嵌Lua的游戏,都是静态链接Lua的,所以既使我能注入,也hook不到那些C API的。