Google
 
Showing posts with label Plugin Framework. Show all posts
Showing posts with label Plugin Framework. Show all posts

2009-12-26

插件打包

  前几天,把所有的插件以插件为单位打包成zip了。不过不知道wxWidgets中的zip虚拟文件系统如何支持密码,至少是没找到相关的选项。现在的情况是验证了方案可行性,可以确认功能上的不缺失以及性能上的损耗在可接受的范围内,但真正实用是一定要有密码的,也就是为了保护插件中的源代码。以前还以为带了密码就不方便第三方开发插件了,现在想通了,其实只要我提供一个插件开发环境,其中自带插件打包功能,这样就可以用同一个密码了。既然wxWidgets没有简单方便的接口来支持密码zip,那就只好自己写一个这样的功能了,好在zip实在是一个很大众化的格式,源代码很容易找到。

2009-12-04

插件支持国际化

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

2009-11-29

当前项目进展及小结

  到昨天为止,基本完成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时发现它只能从源代码中提取出需要翻译的字符串,不能全新的创建一个,这太土了,以后一定要自己写个好用的。
  最后的一个问题是,现在的插件扩展机制,容易出现重复代码,比如相同的功能会在菜单中写一遍,在工具栏中也写一遍,这该怎么修改一下呢,呃,得仔细考虑考虑

2009-11-12

狂烈地崩溃

  昨天偶然发现一个超级严重的问题,程序运行一小会儿就会自动退出,什么提示都没有。至于没提示,这已经有一段时间了,照理说,内部状态、逻辑不正常么,可以给个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这样的大而全的文档极不适应,那些说使用开源的东西成本低的人,不知是真的短视,还是别有用心呢。

2009-11-10

使用SWIG和Luabind的问题续

  这几天似乎渐渐进入了正轨,发现并解决了一堆的问题。
  从github上下载的0.9版Luabind可能不稳定,毕竟是没有正式发布,之前没有经过仔细的试用,后来实际用的时候总是这也不行那也不行,万般无奈之下退回到正式发布的0.8.1版。两个版本确实有不少区别,至少源文件个数都不一样。在实际使用的过程中发现,0.8.1中如果注册一个类时,无论有没有注册它的构造函数和析构函数,都要求它们是public可见的,而0.9似乎没这个限制。
  比较郁闷的是,没找到一个可靠的办法,把一个类中的STL的容器类型的成员变量传给Lua。Luabind中有个return_stl_iterator策略,似乎是可以把它传给Lua,也能在Lua进行迭代,但是在应用程序退出时,就会报未处理异常。我想以后如果真的一定有传容器的需求,可以试一下SWIG。
  在Lua和C++之间传递字符串是个很头痛的问题。C++中的字符串类型太多了,几乎每种框架或大规模类库,都会有至少一种自己的字符串实现类。在wxWidgets中用的是wxString,但跟Lua交互的最好就是C的字符数组。如果是只读的访问,那还是比较容易处理的,Luabind默认处理了std::string类型的转换,SWIG中只要包含了std_string.i,也是差不多透明地处理了。但是如果要把字符串作为输出参数,那就头痛了,Luabind中是有out_value策略和pure_out_value策略,但实际上我发现在Lua中用的时候程序就崩溃了。暂时也不想再深究这个问题了,顶多在必要的时候考虑修改一下接口了。
  又是才发现,SWIG和Luabind中注册的类型是不能互相混用的。比如SWIG中注册的某个函数,返回一个类的实例,而Luabind中又注册了这个类的话,是不能作为那个返回值的类型的。所以有时候可能需要在两边都注册一遍某个类型。比较安慰的是,基本数据类型还是能混用的,呵呵。
  对于有缺省值参数的函数,SWIG会自动封装成多个函数,每个函数使用相同的名字,只是参数列表不同。而Luabind一次只能注册一个函数,那个完整参数列表的函数,如果想要达到SWIG那样的效果,大概只能自己老老实实一个一个注册上。
  再有是Luabind中,不能随便注册char*类型参数的函数,要注册得指定out_value策略,不过说到这里我就有点疑惑,Luabind好像不支持多个策略的啊,如果有多个参数要指定策略怎么办?而且虽然我没经过测试,但我想除了char *外,其他的自定义类型作为参数的,都有这个问题吧!

2009-11-03

划分脚本插件架构的职责面临的问题

  今天整理了一下项目中使用的第三方框架、库的列表,很多,有C++的,有Lua的,突然面临一个迫切需要解决的问题:怎么决定某个功能应该由C++实现还是由Lua实现?
  最早决定让项目成为一个由C++构建主体框架,由Lua脚本扩展实现其他的业务逻辑时,只是单纯得想让C++完成一部分最核心的功能。但现在的问题是,怎么判定一个功能是否够核心,以及即使够核心了,还得考虑其他一些因素,包括实现难度,安全保护,代码架构合理性,代码和逻辑共享等。
  众所周知,用不同的语言实现相同的功能,难度和工作量可能差别很大。以前听同事和领导不止一次说起过,一行脚本顶得上一百行C++代码。这也许有点夸张的成分,但正好说明差异巨大这个事实。引起这种差异的主要原因就我自身角度出发来看,在于对语言的掌握程度,不同的语言风格就对不同开发任务的适应度,以及可利用的现成的库和代码的丰富度和成熟度。
  说起安全保护,是今天看到Lua的maillist上讨论LuaJIT时Mike Pall说起源代码保护时才提醒了我。一般说来,用C++编写的代码,经过编译生成二进制代码,比起用脚本语言写的代码生成的字节码(中间码)反编译要困难得多。而我本来考虑让众多功能都通过脚本实现,这就面临一个问题,如何保护自己觉得重要的代码。最早的时候想用Lua的官方编译器编译一把就行了,现在看来这个保护弱得可以。而最近又面临另外一个短期内不可能解决的问题是,我打算嵌入LuaJIT 2.0的解释器,而刚刚才知道LuaJIT 2.0不兼容Lua官方的字节码,只提供源代码级的兼容,似乎LuaJIT也没提供一个自己的编译器,同时即使有这样的编译器,万一某种情况下需要用Lua官方编译的文件,那么处理就不一致了!Mike Pall的意见是,用个zip之类的东西打包加密就行了,呃,怎么说,确实是个方案,但使得本方案只能自己使用的了,不能让第三方的开发者参与了!所以除非有其他比较完善的解决方案,不然那样的代码只能用C++实现了。
  再说架构合理性,总的说来,到目前为止的进度,自我感觉这样的架构还是比较满意的,当然现在只是一个空壳,只能支持让主菜单、工具栏按钮和右键弹出式菜单的构建和触发都是由脚本插件扩展而成,接着就而对的问题是,像配置选项功能要由谁来做,这种功能有一定的复杂性,又有GUI界面,又有后台处理逻辑,是全部让C++做,还是全部让Lua做,或者是各做一点,那又是各做哪些和各做多少呢?几乎所有同时涉及界面和后台逻辑的功能点,都有这样的问题,究其原因在于,用C++实现了主界面,而在Lua中目前并不能很方便地操作这些界面。原本天真地以为,C++用了wxWidgets做界面,那么Lua中用wxLua就可以实现无阻碍互通了。现实是残酷的,现在光是想让wxLua中使用C++中创建的主窗口作为父窗口就搞不定!
  最后是代码和逻辑的共享。这个问题不是很严重,如果是纯粹的计算逻辑最容易共享,一些常用的底层功能,两种语言都差不多拥有第三方库来解决。除了GUI,其他的代码(逻辑)要共享,通过luabind和SWIG可以比较方便地粘合起来。如果粘合起来还是犹豫不决,那就是架构合理性的问题了。
  想不到这次决定构建一个基于C++的脚本扩展框架的应用程序,会引出这么多问题,大大出乎我的意料啊,我是一直以来对风险的估计不足啊!

2009-10-31

继续改进插件扩展框架再续

  今天完成了右键弹出菜单的插件扩展框架支持,基本上没有遇到什么障碍,跟原来想的一样简单。
  除了这个,还把菜单、工具栏的插件扩展支持功能的代码重构了一遍,把这部分功能提取成一个独立的类,在类中完成插件扩展的相关功能,只有最终的事件消息响应函数仍然放在界面类中,这是因为才发现不是随便一个类的成员函数都可以绑定成事件处理器的。
  顺便说个可能是wxWidgets的bug,动态创建的菜单,动态添加的菜单项,第一个图标总是显示不出来!
  再一个是原本用SWIG生成的文件,我把它直接作为头文件,包含在另一个源文件中,而该源文件因为某些原因,经常会被重新编译,而恰恰这SWIG生成的文件体积巨大(超过25000行),所以编译要花不少时间。于是又仔细看了一下生成的这个文件,发现其实最终只是需要一个luaopen_libname的函数,这样SWIG生成的文件就可以作为源文件了,不用跟着其他文件编译了。

2009-10-30

继续改进插件扩展框架后续

  今天修改了插件扩展的描述方式,把菜单项、工具栏按钮的标题、路径和帮助文本,工具栏按钮的图片等信息,全都写到xml描述文件中,这样一弄,lua脚本确实精简了很多。到现在为止,已经可以正常地通过插件扩展实现主菜单和工具栏的点击响应了,如果要说更新界面状态,也不是很麻烦,也就是多添加一个消息连接而已。
  再说右键弹出菜单,我粗略地想了想,应该不是很麻烦,也就是添加到各自的扩展点下即可。其实直到现在,我才想起来,我这种实现方式,其实应该跟Windows传统的GUI资源编程基本思路是一样的。主菜单用一个编号标识,然后是菜单项信息,需要足够多的信息可以标识出菜单项的位置(路径),然后是给菜单项添加消息响应。工具栏的实现也是类似,所以如果要支持右键弹出菜单,也沿用那套思路就行了。
  昨天说到,如果在插件扩展中使用wxLua,那么wxLua不能使用宿主程序使用的wxWidgets二进制文件,于是我今天想用IUP来试试。我从CVS里取出IUP的代码,然后用MinGW编译出所有的dll文件,可是用的时候发现总是报IupClipboard符号在指定的dll中找不到,而我用depends看是有的,郁闷!但是用LuaForWindows里的dll是可以的,可是它是用VS2005编译的,要带一个VC2005的redist,不爽啊!
  另外一个问题是,本来以为脚本扩展时用了wxLua,而宿主程序也用了wxWidgets,两个之间可以无阻碍地互相使用各种控件,今天才发现,当时太想当然了。我用SWIG封装了宿主程序中的一些代码,比如wxFrame,在wxLua中是不认识这种封装的,两种不是相同的类型。所以现在只能精心挑选一组必需的,常用的代码来用SWIG封装,现在让它生成的包括scintilla和scilexer以及wxcintilla的声明后,生成的文件有近30000行,编译要花不少时间。说起来,我应该再仔细研究一下SWIG的用法先。
  这两天用wxWidgets,有时候感觉它比MFC、VCL要灵活,比WTL要易懂。这也许很偏面。不过我最不满的是它的资料太少,以及运行效率不高。
  

2009-10-29

继续改进插件扩展框架

  这两天又用wxWidgets,不得不感叹一下,资料实在太少了,只有一个现成的manual,其他时候就只有看看CodeLite、Code::Blocks的源代码了!
  到今天为止,修改了脚本扩展的功能,可以在一个描述文件中定义多个扩展的信息。对于主菜单来说,倒是勉强够用了,不过当时因为想让描述文件中对扩展的描述尽量通用,将其他的信息都写到脚本里去了,现在看来如果要对工具栏也使用脚本扩展,那么这种方式实在太不方便了点,还是应该把这种静态配置信息的都放在xml格式的描述文件中,脚本中应该只有动态的逻辑。所以还需要修改。
  今天又忘了,wxWidgets的程序如果使用Lua扩展,而扩展又装载wxLua的话,wxLua的二进制文件不能用和wxWidgets程序相同的wxWidgets二进制动态链接库,不然会出现各种奇怪的问题。这是让我目前比较头痛的问题。我现在是用MinGW来编译wxWidgets和相关工程,那么一来wxLua就只能用VC或其他编译器编译了,但我今天试了OpenWatcom和Borland C++ 5.5,连wxWidgets都编译不过,郁闷!

2009-05-09

总觉得不对劲

  这两天在公司里,做那个插件架构,到昨天为止,大概实现了一半了,不过总觉得不对劲。这是一个没有经过实践检验的方案,看了那些已经完成的代码,再联想一下完全完成后的可能的模样,心里老是有点不爽,担心内存占用会不会太大了。
  现在这个设计是在原来的基础上修改来的,主要的修改是在一个插件中可以定义多个扩展,插件只是扩展的容器,扩展才是真正实现可扩展性的设施。于是相比原来的设计,现在有了两种独立的对象,插件和扩展,而原来一个插件中只定义一个扩展,在代码实现时可以只定义一种对象。于是现在这种情况下,对现实世界的描述,大约会多出近一倍的信息量吧,这些信息如果全部保存到内存中,预计消耗会很大。本来这些信息是通过配置文件描述的,如果不全保存到内存中,就要能实时解析配置文件,这对运行速度也是一个考验,而且能否快速方便地检索到需要的配置文件目前也是个未知数。
  崩溃!

2009-05-04

插件依赖

  因为要使得插件支持依赖,比如在插件描述中说明,某个插件A,依赖于插件B和插件C,而插件C又依赖于插件D,那么在装载插件A之前,需要先装载插件D,再装载插件B和插件C,最后才是插件A。
  这个依赖关于跟C/C++程序代码中的头文件包含处理方式几乎一模一样,典型的做法就是把这所有的依赖关系最终用一张有向图表示,这样通过DFS可以查找到是否有循环依赖,如果有,可以彻底中断装载过程,或者只是简单地把最后一个回边打断而继续装载。
  最终整理出来的插件装载顺序也是很容易的。首先查找没有依赖于任何其他插件的插件,无论是理论上还是实际上,肯定是有这种插件存在的。找到这些插件后,把对这些插件的依赖关系都删掉,这样肯定又多出一些插件是已经解决依赖关系的。再把这些多出来的插件作为被依赖的关系删掉,重复前面一步操作,最终可以将所有插件都顺利装载。

2009-03-30

烦躁

  真的。
  现在一个Lua解释器的封装类,一个插件注册表类,这样的结构是不够用的。至少需要有一个管理插件运行的模块,它可以屏蔽掉解释器的底层差异,使得使用这个模块的用户不用知道他们到底是在运行Lua脚本还是其他Ruby或是Python之类的东西。另外一点是这模块需要向不单是C++代码提供接口。也就是说,用脚本写成的插件需要使用这个接口。这个需求很合理,也很必要,不然的话,很明显的一大限制是不能再对插件进行扩展了。

2009-03-22

果然是编译问题

  因为怀疑,所以去确认了一把。自己也懒得编译了,直接从网上找了个人家编译好的wxLua,人家是用VC8编译的,我的工程是用MinGW编译的,结果真的可以加载了,我狂晕!不管了,大不了到时候提供个Lua for Windows的安装包链接。
  再回来说插件的问题。昨天主菜单是能运行起来了,不过发现有个不爽的地方。因为插件是放在一个指定目录下的,搜索出来的顺序可不是人能控制的,于是添加到菜单上的顺序也是乱的,这就不好了。由此引出另一个需求,对于一个插件应该可以定义多个菜单项,这样至少可以让这一个插件中的菜单项保持可控的顺序。

2009-03-14

插件开发环境

  想起插件的事,如果一个软件具备插件扩展能力,同时又希望其用户自行开发插件,那么这个软件一般说来需要提供一个良好的插件开发环境。纵观Eclipse,MSVS、MS Office等都是这样的,而且VS和Office中的扩展形式除了插件,还有宏,准确地说来,是开发宏的环境是随软件一起提供的,而插件的开发是要用其他工具的,比如VS。而我前面说的插件开发环境,其实对应的是宏开发环境。
  这个开发环境简单的做法,应该是一个独立的程序,但与软件主程序之间有很深入的进程间通信,不然调试功能是不可能那么完善的。也有可能是还有一个中间的宏解释执行器,主程序和宏开发环境都是只与宏解释执行器进行通信,同时该解释执行器也有调试功能。这样做的一个明显的理由是,当调试宏时,主程序可能会被挂起,而这时调试界面则要求是活动的,所以放在主程序中是不容易处理的。
  Eclipse可能不需要这么做,它也许可以在不同的实例中使用不同的配置,这样可以在一个实例中调试另一个实例。
  还有一种比较极端或者不负责任的做法是,不提供开发环境,让插件开发者痛苦去吧。也许用比较高级的脚本语言来做为扩展架构的底层执行器,这个问题的影响会变小,因为脚本语言对调试的需求可能会小于像C++这种编译型语言。但我觉得无论怎么样,提供一点基本的用于调试的设施还是必要的,比如至少可以加入一种打印输出机制之类的。

2009-03-02

基于Lua的可扩展架构设计

  首先,这个架构是一定程度上模仿Eclipse的,并做了大量简化,所以模仿得并不像,不过不得不说最原始的灵感和很多想法是从它那里来的。
  支撑起整个可扩展架构是一个类,大概名叫pluginsRegistry的类。这个类一般可以使用singleton模式,它做一些与插件相关的最基础最底层的事。比如,在宿主程序(这里一般是一个C++程序)启动时,它负责扫描指定的一个目录(包括子目录)下所有文件,找出其中的plugin.xml文件,也就是插件描述文件。它并不一定要是xml来描述,也可以是用Lua来描述,毕竟Lua最早的用途就是作为配置文件的,而且这个文件并不会被程序动态修改,Lua也够用,但要注意的是名字空间污染的问题,一个办法是每个描述文件都使用完全相同的结构和变量命名,但每次都单独启动一个Lua解释器来解析。一般说来一个描述文件中声明一个插件,也就是一些基本的插件信息,包括插件id、插件扩展点和插件对应脚本文件路径这三项最主要的不可或缺的信息,以及一些诸如被扩展点,版本号,依赖关系,版权信息等等不那么重要的信息。扫描到一个插件后,这个类会把插件id,脚本文件路径对应着保存起来,并把该插件添加到指定的被扩展点上。所谓扩展点,其实准确一点说,应该是被扩展点代表一个插件会被激活(其中一部分)的时机。被扩展点一般也是由其他插件提供的,也就是说其他插件会在某个它认为合适的时刻,激活所有注册到被扩展点下的所有插件。所以插件描述文件中很重要的一点是,需要声明自己是扩展了哪个被扩展点,不然就永远不可能被激活。由此可以看出,另外一个需要注意的是,一个插件一般会依赖于另外一些插件,至少是依赖于提供它要扩展的被扩展点的插件。一个插件必须要指明它要扩展的被扩展点,这是别的插件提供的,另外它也可以提供自己的被扩展点,让另外的插件来扩展自己的功能,当然这不是必须的。为了说起来不那么拗口,在描述文件中,一般我称前者为扩展点,称后者为被扩展点,这是把同一类事物在一个所属中以其不同的功能或角色来区别而得来的。再回到这个类的功能上来,除了前面讲的这几点功能外,它还应该能向外界提供一个服务,通过该服务外界可以根据指定的被扩展点查询到所有注册在该被扩展点下的插件,理论是希望只是通过一个id就能唯一标识一个插件。这样每当时机合适时,提供被扩展点的插件就可以通过这个服务来做一些事情,最常见的是通知所有该被扩展点下的插件即可,那些插件自己应该清楚这时应该做些什么事情,还有种情况是,提供被扩展点的插件应该可以从中区分出一些插件,而不是全部,来通知。一个很常见的应用场景,主菜单,可以是一个菜单项对应一个插件,这时每当用户点击了菜单项,那么只要激活该菜单项对应的那个插件即可,而不是所有主菜单下所有的插件都要激活一遍。
  通过前面的阐述,可以知道,这个类需要一个清晰简单的接口,这样才可以比较方便地既给C++代码提供服务,又给Lua脚本提供服务。尽管Lua从设计上就考虑了很多跟其他语言交互的情形,但这里我还是得小心一点,少使用一些只有某种语言特有的一些元素或特性。
  最后,以一个实例来简单叙述一下这个架构是怎么真正让一些功能运作起来的。就还是以主菜单为例,主菜单也是一个插件,当然,它是用C++还是用Lua实现的我们不关心。重要的是主菜单插件首先有一个自己的id,并提供了一个被扩展点,假设叫view.ui.menu,这时假设那个类已经把所有扫描到的插件正确地分析过了,这个被扩展点下已经有一些插件了,比如file_open,file_save等等。主菜单插件在自己被初始化时,调用那个类来得到所有view.ui.menu下的插件,并依次激活这些插件的get_caption方法,根据这些个方法返回的内容,作为菜单项的标题,逐个添加菜单项,并把菜单项的id和它对应的插件(可能就是用插件id来表示)保存起来,当用户点击一个菜单项时,主菜单插件可以得知用户点击的菜单项的id,并从之前保存的信息中得到它对应的插件id,以此来再调用那个类来激活唯一对应的那个插件,那个插件知道用户是点击了菜单点,就执行一些操作,比如打开文件等等。
  大致的流程就是这样。

2008-04-25

从扩展编辑器开始

  重构auto completion和call tips模块,因为总体设计都被改得天翻地覆,而且决定开始使用TDD的方式进行,所以进度相对来说慢了些,更因为我自己一直静不下心来好好干活,真是令人烦恼啊!
  话说其他一些相对可独立分类的小特性大部分已被我清理出来,但剩下还有一些杂七杂八的功能,我却不想再动了,因为从很早的时候就考虑好了这部分功能不应该由C++代码完成,而应该由host提供的扩展机制使用外部extension实现。但是现在的情况看来,这扩展机制短期内是不会加入的,因为老大似乎不支持这样的做法。另外一点是,组内大概除了我,也再没有其他人有足够深厚的兴趣和迫切的需求实现这么一个框架。
  最早虽然想到了要用扩展,但并没有考虑很多,只是觉得只要能用内部嵌入的那个Ruby解释器执行一段脚本,而那个解释器同时也能访问到内部的一些数据结构和算法,应该就能处理目前的需求了。当时预见得到的需求也确实简单,比如点击一个菜单项,在当前光标位置插入一个字符串,该字符串可能是当前日期或时间等等。我当时想法相当单纯,甚至想的是菜单项还是依旧在资源管理器中画好,消息映射也还是添加好,最后实现的时候再调用Ruby解释器解释一个外部的脚本文件就可以了。即使这样简单,也比纯粹使用C++实现有一定的优势,至少如果需要修改实现逻辑,就只需要修改那个脚本文件,不再需要修改C++代码,不需要重新编译。
  但是自从前几天快速翻看了一遍《Contributing to Eclipse》之后,我就想,要做就做得灵活点,也许不要求完全跟Eclipse一样那么强大有弹性,但也至少要满足以下几个要求:
1、在主菜单、弹出式菜单、工具栏(、状态栏)上都可以扩展;
2、在扩展之上还可以预留扩展点继续扩展;
3、既然用脚本扩展,那么扩展加载应该也像Eclipse那样“懒加载”;
4、需要有一种打包策略,足以支持起复杂的多文件的扩展。
  暂时就只想到这些,嗯!重构完auto completion和call tips,就悄悄做这个。

2008-04-24

讨教Plugins设计

  下午去向人讨教了一下人家怎么设计实现Plugins机制的。据该人士讲述,当时他们的产品2.x版本面对各种千奇百怪的用户需求,以及进度压力(这与我们目前遭遇到的情况类似),经过研究COM相关技术和Eclipse(估计当时也是2.x版本)的Plugins机制,最后在3.0版本实现了目前(也是3.x吧)的Plugins机制。
  首先,该软件产品一般的插件都是通过vbs脚本实现,他们内嵌了vbs脚本解释器,使用的当然是最流行最正规的IActiveScript之道。可以扩展的地方包括主菜单、工具栏、弹出式菜单、状态栏等。像Eclipse及其他很多软件一样,基本的界面元素是用XML描述的,以弹出式菜单为例,在XML中首先会定义视图之类的信息,而弹出式菜单是作为视图信息的子节点出现的,说明这个弹出式菜单是在视图上点击右键才会弹出。这里好像是XML里就直接指定了菜单项的ID,对应Windows程序里的命令ID,而ID范围是程序中已固化规定好的,不能超出这个范围。然后指定一个脚本文件,可能还会具体到其中某个函数,每当菜单项触发时,便调用这段脚本。可能还会有触发前、触发后的选项,可以继续使用自定义脚本扩展实现。另外,菜单项有一个状态,如disabled、checked等等,这是在另外一个属性中设置的,该属性同样是指定一个脚本文件或函数,刚好对应MFC中的ON_UPDATE_COMMAND_UI。还有一点是,菜单可能是针对某种具体的选中项才有项的,所以在XML中描述菜单时,可能会包含一个属性,用于指定对其有效的选中项类型,只有在该类型的界面元素被选中并在上面点击鼠标右键时,才会弹出这个菜单。再有就是菜单等界面元素不可避免的一个问题是国际化,他们是把各种语言对应的字符串信息分别写到不同的配置文件中,描述菜单信息的XML里通过指定个别名或ID来动态查找装入相应的字符串。如果激活扩展插件函数时,需要一些固有信息,而是通过规定的格式书写函数参数。
  其次,除了支持脚本扩展,该软件还支持COM组件形式的Plugins,这种Plugins相比脚本Plugins肯定强大得多,也许就好比Firefox中的Plugins之于Extensions,不过在介绍时他们一带而过,似乎也不是很支持第三方的去使用这种机制。
  最后说一点,为了实现能让vbs脚本访问host程序中的一些信息或方法,他们把所有的东西都用COM接口还实现。似乎这是目前而言,我们能找到的最简单的一种处理方式,所有的状态、中间数据信息都可以保存在host程序中,脚本应该只是描述逻辑,而不保存状态。但这也是我最不屑的,我还是比较欣赏Eclipse(3.0以后用OSGi的不知道有多少变化)的机制,希望我有机会能设计并把它实现!

2008-03-06

扩展性和通用性

  这两天在想一些问题,怎样构建一个有足够扩展性和通用性的框架呢?
  对Scintilla的使用有了一点经验后,就想好好利用它来做几个有点实用价值的东东,关键就在于想做的不止一个,但在文本编辑方面由于都是使用Scintilla,所以肯定会想到如果能让代码写得足够通用,所有工程都共享一个代码实现就好了,以后即便有了修改,只要修改一次,其他的最大程度上只要重新编译链接一下就可以了,不用再修改源代码。但这样的通用性要如何实现呢,组件化是一种常见的解决方案,这里Scintilla已经是一个现成成熟的组件,但它只是提供了最基本的能力,如果要把这些能力展现出来,还是自己写代码实现。
  有鉴于Impeller项目越来越冗长庞杂的实现,我心里很是不舒服。它确实有那么多事要做,也许有些事不是它的责任,但那也只是极小一部分,但把所有事的实现都放在工程中用C++完成,我就觉得有待商榷了。比如有很经典的一段代码,用于在用户动作触发时,在当前光标所在位置插入日期和时间,这样微不足道的功能,花了好几十行C++代码。其实在大半年前,我还在维护编辑器模块时,我就想通过脚本来支持这些简单功能了,但后来因为这样那样的原因最终没有实现。这只是一个简单的例子,照我的想法,最理想的方案是程序从配置文件中读出相关扩展信息,根据配置在主菜单、工具栏、弹出菜单等用户最习惯的界面上添加可触发控制,当用户触发了这些控制时,程序从配置中读出需要执行的动作,可能是一段脚本,或一个脚本文件,或一个DLL导出函数,甚至是一个COM组件中的一个接口中的方法,总之在被触发前这些东西可是是实际不存在的,也即Eclipse提倡的懒加载规则。这里有几个实现上的难点,如何通过配置文件就动态地实现配置的用户界面;如何通过用户动作立即找到正确的执行动作;如何让主程序和扩展进行交互。一般说来主程序需要提供一些方法供扩展进行调用控制主程序的行为,于是有相应的问题是主程序通过什么方式暴露这些方法,又需要暴露哪些方法。如果这些问题都能解决了,还有另外一个问题,不同的扩展之间是否可以交互,它们的协议应该怎么设计。
  总之在动手做那几个东东前,这扩展性和通用性的问题必须要有一个可靠可行的方案,不解以后自己得累死。

2007-12-20

Bug多多

  其实静下心来想想,这也是正常的,这样匆匆忙忙赶出来的东西,质量能好到哪里去,而且事实上不但稳定性差,易用性也是极差。照我自己的估计,再给两个月,才能基本到可用的程度,即稳定性和易用性都为大多数普通用户接受的程度。
  心里好烦啊!
  中午在菠菜里吃出一条虫子,我都怀疑我有没有吃下去更多条,晕!拿去换了10块钱和1盘西瓜,然后去买卡士喝了。疯丫头也吃出一个小蚊蚊,这是以前某人常用的叫法,然后也是换了10块钱和1盘西瓜。
  唉,我终究是很普通一人,有时候确实太高估自己了。
  asio用了这么些天,还是没掌握具体用法,用来用去还是问题一堆。
  COM也是很烦人一东西,无奈的是有些时候不得不用COM啊,再加上组里最有话语权决策权的几人,都是COM的忠实粉丝拥趸,郁闷。也不知道是不是我自己太高傲了,总是要跟别人唱反调。但总是觉得人家确实也没多少让我佩服的地方,唉。
  对于一牛x的编辑器,以UltraEdit为榜样来说,要直接支持脚本扩展,可以通过方便地激活一外部脚本,来操作编辑器,比如移动光标,输入字符,查找替换,复制粘贴,删除撤消等等,这是一类最基本最通用的扩展方式,可以有任意数量的脚本。另外一类方式是,可以配置宿主程序的界面,比如增加菜单项,增加工具栏按钮,而且这些菜单项和按钮的位置、文字、图标等内容是可以定制的,当然点击后,就会触发一个脚本的运行,这种脚本也可以有任意数量,任意配置。还有一类方式,是事件处理脚本,当宿主程序遇到某一事件时,就会执行相应的脚本,比如打开一个文件时,文件关闭时等等,理论这类脚本也可以有任意多个,但实际上不能太多,因为如果某事件触发时,结果要分别运行一堆脚本,这种性能问题应该会比较明显,并可能让用户受不了。暂时想到的是这3类脚本扩展支持,最近又因为对多种脚本语言的简单接触,又没有哪种让我觉得特别喜欢,所以我反而想要能同时支持多种脚本语言的扩展。这时,SWIG出场的机会来了,至少Python、Ruby、Lua、TCL都是被它支持的。
  除了可以用脚本扩展外,是否还要提供2进制扩展接口,这点我还不太确定,2进制扩展可以有普通DLL和COM两种方式,差别不大,只是怀疑其必要性,它有多少价值,多少能力,有多少情况下需要它出场解决问题。
  最后一点是,正则引擎需要能挂接,随时任意替换吗,UltraEdit是这样做的。

2007-05-13

从XUL说起

  心血来潮,又去Songbird的网站看了一下,还把编译好的Preview的安装程序,以及SVN里的源代码都下下来了。只可惜看了半天的编译说明,还是没看懂,唉,这个说明也太烦琐了,条理都不清晰。这样看来,Mozilla的XUL解决方案怎么推广得了哦,想想Adobe、MS这些大厂商的解决方案,一整套完整的流程支持,各个环节都可能会有比较完善的工具支持,而且安装部署都会很容易,一张光盘,一个ISO镜像,一个setup.exe文件就搞定所有的事,一切都准备好给人使用,有多少比例的人愿意去花那么些时间搞那么麻烦的mozilla的东西。Mozilla社区确实应该好好想想,反思一下,为什么XUL技术出来这么多年,Mozilla里也用了那么久,却一直没有被大众接受,直到现在这些大厂商出来推,才火起来。也许从技术的角度讲,各个解决方案间没有特别大的区别或差距,但是易用性、友好性方面呢,可能差得太多了。其实这也是众多开源项目的通病啊,孤芳自赏的hacker想法是走不出hacker圈子的。
  装上Songbird,听了几首MP3,就把它卸载了,现在的条件用XUL技术来实现这样的桌面应用时候还早了点,连一个窗口里点菜单,点右键,来回换输入焦点都会卡好几秒钟,有几个人受得了,换回foobar2000,看起来轻巧多了。所说Firefox3.0也是基于XulRunner来实现的了,就像Songbird一样,进程管理器里可能就看不到firefox.exe了,而是一个XulRunner.exe。这样就像Java之类的了,虚拟机实现的东西,除非是开发效率或其它有什么特别突出的优点,不然是不会被大众接受的。
  Mozilla能有那样好的扩展能力,其实从某方面讲,跟Eclipse、Emacs等是一个原因,那就是脚本语言和本地代码的结合。它用XUL描述界面,用C/C++实现真正的逻辑,再用JavaScript把两者结合起来。这样看来,只要能把描述界面的部分规范下来,真正的核心逻辑用C/C++实现,再设计一个良好的两者互动通信机制,那么一个弹性极佳的系统也就设计出来了!LLYF Spy中,是应该好好设计一下了,2.0可以只做预研性的设计和实现,3.0可以考虑实现成一个程序员工具平台了,只要扩展机制实现得好,可以给它添加各种扩展功能,比如编辑器,比如听MP3,比如下载上传……