All Stories

从腾讯开放微博API说起

  腾讯也开放微博API了,这对于一个一贯坚持封闭的公司来说,真是个很艰难的决定。但是,眼下的互联网环境中,开放已经成了大趋势,想想Facebook和Twitter这两种SNS的代表形式,由于API的开放,第三方的应用和客户端真是百花齐放,争奇斗艳,不开放就是死。腾讯在推出微博服务的时候,我猜他们一点都不担心自己的用户数,因为他们自以为有庞大的QQ用户基数支持,只要让QQ客户端支持微博,就能顺利将用户转移过去。事实是国内使用微博的用户,绝大多数不是使用网页端,就是使用手机客户端,同时可以想像得到,目前的QQ死忠用户鉴于他们的年龄,知识构成,社交习惯等因素,很难迅速接受并支持微博这种表达方式,对他们来说,QQ签名就足以支持他们对微博这种媒体形式的需求了,那样更方便,更直观。   开放API,并不是说将微博功能从网页端到其他桌面端或手机端实现一遍就完事了。Twitter在这方面无论是官方还是第三方都有不少的尝试,当然这也跟服务商对微博的定位有关系。比如Twitter之前说过他们做的不是社交服务,而是新闻服务,提供的是内容,所以Twitter官网的上次大改版就是往这个方向上发展的,它更注重内容的展现,希望用户可以方便快捷地获取到各种信息。而国内的诸多微博服务,无一不是停留在最原始的框架下,对于信息的回溯检索,或是扩散都没有任何明显的形式的支持。就像之前ifanr对和菜头的访谈中提到的那样,国内的微博在意识形态上仍然停留在精英制造内容,草根膜拜瞻仰的原始阶段。   这些天推友@lucifierya一直在推一些他对个人知识管理的想法和实践,他主要偏重从网络上获取的信息,包括RSS、Blog、Tweet等。其实这三部分独立的,都有各种实现得很好的方案,两种或三种结合的也有一些方案,但显然就很不被重视了。再回头说腾讯,腾讯有QQ阅读器,有QQ空间,也有微博,三者也有简单的关联,比如QQ阅读器中可以看到好友最新文章,但也仅此而已了。我对@lucifierya的一些想法深以为然,但也许因为他个人缺少对软件开发方面的知识和技能,他提供的解决方案就有点畸形。我在上Twitter的这一年来,一直在考虑自己实现一个Twitter客户端,一开始只是一个单纯的客户端,后来想法就逐渐有所延伸和扩展。单纯的客户端并不是我想要的,我的本意是需要一个获取信息,管理信息的工具。考察和试用了不少其他的服务,想法已经比较完整,但尚未明确和具体,大致如下:   1、连接各微博服务,当然首要是Twitter,提取微博中的网址超链接,作为一个信息源。   2、连接各书签分享服务,比如Delicious,ReadItLater,Instapaper等,作为第二个信息源。   3、订阅RSS以及Google Reader,作为第三个信息源。   4、方便地将超链接分享到微博和书签服务中去。   5、将网页主要内容提取显示。Readability、iReader等扩展的作用很明显,可以极大地提高用户体验,但将这功能放在浏览器中需要用户手工激活,有点繁琐,而在专业的内容阅读器中默认采用这种形式就方便了。   6、将所有内容条目以列表形式组织显示,如Feedly或Reeder那样,可以看到每个条目的开头的一部分信息以及内含图片的缩略图,用户就可以直接判断是否是自己感兴趣的内容。   7、将所有内容条目以报纸排版显示,如QQ阅读器或http://paper.li那样,这样除了有上一条的好处外,更多的是一种贴近传统习惯的用户体验,至少我个人是相当喜欢这种形式,只不过QQ阅读器一页显示的内容太少,以及不能自行添加RSS订阅,而http://paper.li时有乱码,更新周期太长,并且导致有时候一页内空白块太多,不够美观。   8、用户可以对内容添加评论,评论内容和相关链接可以保存到Google Docs上去,这样就可以移动应用了。   以上这些就是我总结出来的大的需求,其他小需求暂且不提。我会自己用Qt慢慢实现,以目前的能力来看,至少可以实现跨Windows/Mac/Linux/Symbian/MeeGo了,也许以后Qt移植到Android和iOS上后,也会支持Android和iOS吧,这是后话了。

QOAuth编译后运行不正常

  现在不少服务都用OAuth认证了,在Qt下有QOAuth这个第三方库,它是个日本人为qTwitter这个Twitter客户端而编写的。要编译QOAuth,有几个步骤。   首先,编译QCA,这是个KDE下的项目,Qt下的加密算法库封装。   然后下载OpenSSL,需要头文件和导入库文件。编译qca-ossl,即连接QCA和OpenSSL的Qt库插件。   最后便可以编译QOAuth了,需要链接QCA和OpenSSL。   今天我试着用MinGW和MSVC2008编译QOAuth,除了编译qca-ossl时发现有个whirlpool算法分支编译不过外,其他的都编译出来了。接着我就想编译一下Qwit和qTwitter来测试一下QOAuth,结果编译倒是都能编译出来,不过最后运行有问题,在认证Twitter账号时,程序会死循环,不知道是OAuth认证的3个步骤中的哪步出错了,初步估计是第一步,因为连浏览器窗口都没弹出来。也不知道是几个库中的哪个库有问题,唉!

怀念测试组,想念马姐姐

  在GTalk上聊天,说起同事集体活动,突然思绪飞回2005年了。我入部门的第一个周六便是项目组活动,去深圳东门的工人文化宫溜旱冰。我并不会溜,所以摔了半死,不过后来倒也勉强能滑一会儿了。   然后我想到了马姐姐,当年那个号称“光网络第一美女”的主管大人。我进测试部一直是一种说不清道不明的奇怪体验,因为我其实只想做开发的,记得到部门报到的那天,项目部的老大跟马姐姐交接了我们两个新人后,轻声跟马姐姐说我的培训计划要调整一下。这是我到现在都没明白是怎么回事的事。   在测试组呆了22个月,其实应该只有21.5个月,但印象却深刻得多,令我怀念得多。在测试组我的绩效一直不好,一直测不出产品问题,直到3个季度后通过代码审查才能凑够PBC指标。然后俨然一副(伪)高手的样子,哈哈。后来项目组迁移到成都,直接主管问我愿不愿意去成都,我不假思索地说不愿意。之后就直接把我释放到系统部去了,而听其他的同事说,他们都是至少被问过两次的,这也是让我觉得奇怪的事情。   还有件事也是不吐不快,这事一直是我们那拨人的谈资。测试部组织C语言考试,题目出得比较变态,要求75分合格。我们测试组的一帮人,咳咳,通过大家都知道的手段,有4个人考到80分以上,其中一个就是我。结果大条了,貌似其他组就没人合格的,就我们组就被整了,另外三个同事被叫去,结果当然是老实交代了。最意外的是我没被叫去,他们都说是看了我的答案。马姐姐作为我们的主管,当然事情就捅到她那里了,她就问我怎么回事,我一脸愤恨的说没办法,被逮住了是我们倒霉。现在想想当时的我真的挺欠揍的,呵呵,想来只要老实点认错,马姐姐的立场看怎么滴都是要保我们的。当然这事后来也是不了了之。   总之,从一个局外人的角度看,当年我在测试组的表现,可以说是成事不足败事有余,却莫名其妙的桀傲不训,作为我的主管应该很头痛吧。我转正后的第二个季度只完成了PBC一半的指标,于是自然而然被打了个D,我非常郁闷,思思玉玉和小妞都安慰我鼓励我,马姐姐和我的直接主管找我谈话,说是对我还有期望,不然就直接开了。我后来偶尔想起这个事了,就觉得有点诧异,为什么会给我个D然后又不开我呢?总不至于真是因为说这个招聘成本比较高,然后神马的吧,总觉得这说法不太靠谱。   马姐姐是个比较时尚的人,呃,这样说应该没什么大错吧。她喜欢运动,貌似打羽毛球很厉害,不过我没去看过她打球。她喜欢唱K,我们好几次活动都去唱了K,导致后来我们组的人很多都比较喜欢唱K。不过马姐姐有点色色的哦,喜欢跟组里几个唱歌好的帅正太对唱的,哈哈。哦,突然想起来,有一次活动,好像是他们下午泡温泉我没去,晚饭后又可以唱K打桌球神马的,我们在K厅里唱啤酒,马姐姐拿了我的那杯啤酒喝了一口呢,各种囧。   我们那么些年来一直在猜测马姐姐的婚姻状况,一直没得到确切的消息。直到我辞职的那年,呃,就是去年吧,才通过各种八卦得到比较准确的信息。那时我们已经都在不同部门了,偶尔还一起吃饭,马姐姐作为一个女人当然也喜欢八卦了,问起在坐的每个人的个人终生大事,最后终于有人忍不住问了一句“那你呢”,然后马姐姐就一然淡然地说“我结婚了啊”,再加一副“你们居然不知道,不知道应该是你们的错吧”的表情,但是至于是跟谁结的婚她就闭口不提了。后来跟王同学和她老公一起吃饭八卦,说起来马姐姐开的车跟某男开的是同一辆车,那时我们才比较坚定地相信这个消息应该是没错了。   最后再提一件我有点诧异的事。有一次在食堂吃饭,然后又说起男女关系什么的,说到我时马姐姐来了一句“YF(我的名字)眼光高”,我瀑布汗,她怎么这么看我,虽然可能也没错!   辞职的这一年多来,经常怀念在深圳的工作和生活,想念那些可爱的同事们。

wxWidgets项目中接管未处理异常

  wxWidgets本身支持对未处理异常引起崩溃的拦截和后续处理,从它的官方文档中可以看到,只要在wxApp::OnInit()中调用::wxHandleFatalExceptions(true);然后覆盖wxApp::OnFatalException函数,可以比如遍历当前进程的堆栈什么的。   还有种处理这种未处理异常的方法是在程序启动时(一般就是在wxApp::OnInit()中)动态装入exchndl.dll文件,接下来该dll会自动拦截未处理异常,并在崩溃的时候自动在exe文件所在目录生成一个与exe文件同名的.rpt文件,里面以纯文本记录了进程上下文等现场信息。exchndl.dll可以在这里找到,可以看到其实它是配合一个叫Dr MinGW的现场调试器使用的,这是MinGW的方案。   如果是用MSVC编译器,那么有更好的方案,那便是google breakpad,它实现了Windows、Mac OS X、Linux等多个平台的dump,并且除了未处理异常,在MSVC2005之后的编译器中还包括了纯虚函数调用和CRT函数调用参数错误的捕获,另外提供了文件上传等服务,具体的编译部署可以参考这篇还有这篇文章,不过我的实际使用gyp经验是,应该在breakpad的gyp所在目录开始运行python.exe yourpath/gyp --no-circular-check breakpad_client.gyp,不然会报递归溢出。之后就只要在程序运行范围内创建个google_breakpad::ExceptionHandler对象就可以了,它有各种选项,提供了很多功能,可以通过阅读google breakpad的源代码获得详细信息。   值得说的是,这两种接管未处理异常的方案可以同时使用,但就我个人意见是,如果是用了MSVC编译器,配合PDB文件进行dump文件分析得到的信息已经足够了。   最后提一下另一种拦截未处理异常的方法。除在前面提到两种方法外,e使用了另一种方法,它没有在wxApp::OnInit添加任何代码,而是自己添加了一个WinMain函数,是的,MS官方文档给出的Windows程序的入口,在该函数中调用::wxEntry即可,在该函数后面添加IMPLEMENT_APP_NO_MAIN(CXXXApp)而不是平常用的IMPLEMENT_APP(CXXXApp)。这时,WinMain中就可以为所欲为了,比如e是自己写了__try{}__except(){},也可以根据自己需要,用google breakpad或者exchndl.dll什么的。

互不兼容的Luabind/SWIG/wxLua

  今天开始整理代码,突然又觉得现在嵌入Lua的方法实在太丑陋了。   为了连接C++和Lua,我用了Luabind、SWIG和wxLua。而主要问题在于这三者在映射C++的自定义类型时对同一个C++类型做出了不同的映射,所以不能互相兼容使用。   最早决定是引入wxLua的,因为一开始是确定用wxWidgets框架,并嵌入Lua做扩展,于是必然的Lua会要操作wxWidgets的类型,同时Lua扩展也会需要有一些界面需要画。但实际上,当时没搞清楚使用wxLua到底能不能满足这些需要。而经过真正使用后,才知道wxLua中的类型并不能直接用于让嵌入的Lua解释器来映射到宿主程序中的wxWidgets的类型。于是现在的状况是,wxLua和宿主程序的wxWidgets是互不相干地使用着。wxLua仍然用于画些对话框,做些文件IO之类的操作。宿主程序的wxWidgets组件却是由Luabind和SWIG来实现粘合的。   使用SWIG,是由于要在Lua中操作wxScintilla类型,而Scintilla有几千个常量,然后自己又定义了一堆方法,于是用SWIG感觉是顺理成章的。   而使用Luabind,只是觉得它的call方法很方便,基本可以做到无视函数参数个数和参数类型从C++调用Lua函数。只是马上发现,通过Luabind来调用Lua函数时,把C++对象传递过去时,即使时实际上的同一个C++对象,在Lua中表示的类型却是Luabind中的一份,跟SWIG中注册的类型全无关系,跟wxLua中的也全无关系,比如说一个wxString,三个工具/库,有三种表达方式,互不兼容。   于是我就觉得很丑陋了。突然就有种冲动,自己实现那么一套东西,应该是集合了SWIG和Luabind、wxLua的特点,却又能互相通用。也就是说,有工具可以自动扫描C++声明,生成胶水代码,又有库,可以自由绑定C++类型,同时又提供对wxWidgets,甚至Qt和Gtk+的绑定。其实说起来这并不是非常困难的事,只要修改一下SWIG生成代码的方式,以Luabind的方式生成胶水代码就可以,而wxLua则也以Luabind进行绑定,这样应该就是我想要的那套东西了吧。

Qt Creator插件工作流程代码走读

  Qt Creator有个很风骚的插件管理器PluginManager,还有个很骚包的插件说明PluginSpec。基本上,所有的Qt程序的入口都是传统的C程序一样,代码流程从main()函数开始。   在main()中,先初始化用于国际化的translator,然后获取程序配置settings,接着就在栈上创建了PluginManager对象,之后为PluginManager设置搜索用的文件扩展名pluginspec,设置配置,再设置插件搜索路径。   设置好插件搜索路径后,PluginManager会从配置中读出被忽略的插件列表和需要强制使能的插件列表,然后开始在插件搜索路径中查找*.pluginspec文件,这类文件中记录了插件的名称,版本号,依赖插件等信息。找出所有.pluginspec文件后,就检查一下每个插件所依赖的插件的名称和版本号信息是否匹配。   接着再返回main()中,找出Core插件,该插件是整个Qt Creator的主框架,甚至实现了主窗口。如果Core插件有问题,Qt Creator就会打印出错信息后主动退出。   最后便是调用PluginManager::loadPlugins()载入所有插件。loadPlugins()首先调用PluginManager::loadQueue()以确定插件载入的先后顺序,该过程做了两件事,一是检测是否存在循环依赖的情况,这里用了一个很简单的方法,将当前的插件放入一个队列中,然后检测它所依赖的插件的依赖插件,如果新检查的插件所依赖的插件在队列中,那么说明存在循环依赖。另一件事便是安排载入顺序,同一个函数内通过递归,先递推将最被依赖的插件放入队列,然后回归将最后的插件放入队列,这样生成的便是解决了载入顺序问题的队列。得到载入顺序的队列后,便依次调用PluginSpec的loadPlugin()方法,这里它将插件状态定义为Invalid、Read、Resolved、Loaded、Initialized、Running、Stopped、Deleted共8种,loadPlugin()方法根据传入的要求的状态,进行相应的操作。Loaded时通过Qt本身支持的插件机制,装入动态链接库,之后是Initialized状态,调用每个插件的initializePlugin()方法,最后是Running状态,调用每个插件的initializeExtensions()方法。其中initializePlugin()和initializeExtensions()并没有多少区别,调用的时机也是挨着的,中间没有其他的操作。一般可以简单地这样区分,initializePlugin()中完成最最基本的插件初始化工作,包括创建插件内部的一些对象等,而initializeExtensions()中则完成那些内部对象的初始化工作。当然也可以不用严格遵守这种规则。   到此为止,整个Qt Creator就运行起来了,消息循环启动后,用户就可以进行操作了。

Code::Blocks插件工作流程代码走读

  在app::OnInit()中,插件管理器先设置了一个安全模式,安全模式只是影响后续插件是否被激活。   在MainFrame的构造函数中,调用MainFrame::ScanForPlugins(),该函数根据配置又调用插件管理器PluginManager中的方法ScanForPlugins()扫描指定目录下的文件,该方法在指定的目录下查找dll(Windows系统)或so文件(UNIX系统),同时,需要确认当前是否以BatchBuild模式运行。所谓BatchBuild模式,可以这样理解,Code::Blocks启动后只是用于编译工程,不进行其他诸如代码编写之类的操作,所以除了编译器插件,其他的插件都不需要载入。每找到一个dll或so文件,PluginManager又会去找同名的zip文件,从zip文件中提取出manifest,manifest文件中保存了插件相关的版本号,作者,联系方式等无关紧要的信息。之后PluginManager::LoadPlugin()方法被调用,在该方法中实现了系统层面的动态链接库load并创建插件实例对象的工作。而且从这里可以看出,一个dll文件中可以有多个插件,PluginManager会依次将该dll中所有插件都创建实例对象。   接着MainFrame::ScanForPlugins()又调用PluginManager::LoadAllPlugins()方法,这个方法做的事情跟它的名字有点不对应,因为照我们传统的理解,之前PluginManager::LoadPlugin()已经完成了插件载入的工作。而这个LoadAllPlugins()方法只是调用了每个插件的Attach()方法,该方法做的事情可以归类为插件初始化工作,每个插件都可以有自己的独立的OnAttach()方法被PluginManager间接调用。并且每个插件类都从wxEvtHandler继承下来,可以让自己处理一些事件,比如响应菜单项点击或工具栏按钮点击等等。在Attach()中会将自己这个新创建的实例对象插入到MainFrame的event handler chain中,这样才能将事件响应流向插件实例对象。实际上Attach()就做的有意义的事只有使能了事件处理响应。   最后,便是做些插件在界面上的处理。比如在创建主菜单时,会让有需要的插件自己添加菜单项,并绑定事件处理函数。到此为止,插件就基本上可算是能正常工作了。

关于插件机制的补充

  首先,之前我说过,现在的CodeLite没有一个很大的包罗万象的dll了,这个说法不是很准确了。因为确实仍然有一个叫libcodeliteu.dll的文件,只不过里面包含的内容确实不介Code::Blocks的那个dll是包含了完整的几乎所有的功能,而是只包含了一些跟IDE业务关系不大,却是比较底层而又基础的通用功能。   其次,CodeLite的插件使用C链接的方式导出几个知名函数,用于创建插件对象,查询插件信息等。而Code::Blocks的插件则不然,它们没有这样的知名函数,而只是从几个规定的插件基类中选择一个进行派生,而这个派生类又声明成dll导出,同时,又在定义插件的cpp开头处定义一个全局对象,用于注册本插件。该全局变量接受两个参数,分别是字符串类型的插件名字,以及泛型的插件类类型(也就是说,是个模板参数)。注册插件的那个全局对象保存了插件类类型,在需要的时候可以生成一个插件对象。所以这样可以省掉那几个知名函数,代价是需要导出插件类,容易暴露过多实现细节。感觉CodeLite的方法比较传统,但应该适用于多种编译器,而Code::Blocks的方法就Quick & Dirty一点,跟C++编译器的绑定就比较紧密了。从软件工程的角度讲,我倾向于CodeLite的方案,从项目进展的角度讲,我倾向于Code::Blocks的方案。   最后提一下,Qt Creator的插件也没有知名函数,但具体是怎么载入的,我还没仔细看过。Qt Creator的插件拥有多种状态,这点太完备了。

Qt Creator插件机制简单分析

  Qt Creator的插件机制比起CodeLite和Code::Blocks来要强大得多。每个插件至少有一个dll文件(以Windows平台为例),还有一个必须的.pluginspec文件。插件的基本信息在.pluginspec文件中描述,包括插件的名称、版本、版权、作者、基本信息、分类、网站等,最重要的一点是有依赖的插件信息。支持插件依赖可以获取得大的灵活性和可扩展能力,这点CodeLite和Code::Blocks都没做到,因为单靠一个动态链接库很难实现这种依赖关系的描述。   插件的实现同CodeLite和Code::Blocks类似,都放在dll中,但它的实现有点像是糅合了两者的作法,但又有所改进。Qt Creator应用程序本身的exe基本上只是实现了一个插件管理器,其他的IDE相关的业务逻辑全是由插件实现,甚至于主窗口也是放在一个叫Core的插件中实现。Qt Creator又定义了一组组的接口让插件实现,这点看来跟CodeLite比较像;但是它又基本上将接口的实现,至少是头文件全暴露了,插件在实现这些接口的时候可以直接调用它依赖的插件的服务,而不是通过接口调用,这点上看又有点像Code::Blocks的做法,我个人不是很喜欢这种暴露过多信息的做法,尤其是如果作为一个商业项目,应该尽量少地提供各种内部信息,当然Qt Creator本身作为一个开源项目,大概在这方面就比较随意了,如果全部用接口的形式交互的话,大概工作量会增多,跟实现一遍COM差不多了。   另外说一下,Qt提供了几个宏,用于定义插件类和使用插件,而且Qt框架本身的一部分特性也是通过这种插件机制实现的。只不过这种支持实在太过简陋,只能说了胜于无吧。