<!-- [if !mso]><!-- [if gte mso 9]><![endif]--><!-- [if gte mso 9]><![endif]--><!-- [if !mso]><!-- [if gte mso 10]>
XPCOM开发学习文档
陈彦旭
2009-3-30
目录
<!-- [if supportFields]> TOC /o "1-3" /h /z /u <![endif]-->1Mozilla的架构设计-- <!-- [if supportFields]> PAGEREF _Toc226176905 /h <![endif]-->3<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.1分离界面和实现-- <!-- [if supportFields]> PAGEREF _Toc226176906 /h <![endif]-->3<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.2 针对接口编程-- <!-- [if supportFields]> PAGEREF _Toc226176907 /h <![endif]-->5<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.3分层设计-- <!-- [if supportFields]> PAGEREF _Toc226176908 /h <![endif]-->6<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.4可扩展性-- <!-- [if supportFields]> PAGEREF _Toc226176909 /h <![endif]-->7<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.5可移植性-- <!-- [if supportFields]> PAGEREF _Toc226176910 /h <![endif]-->7<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
1.6稳定性(内存问题检测) <!-- [if supportFields]> PAGEREF _Toc226176911 /h <![endif]-->8<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2XPCOM简介-- <!-- [if supportFields]> PAGEREF _Toc226176912 /h <![endif]-->8<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.1XPCOM 解决方案-- <!-- [if supportFields]> PAGEREF _Toc226176913 /h <![endif]-->8<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.2Gecko- <!-- [if supportFields]> PAGEREF _Toc226176914 /h <![endif]-->9<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.3组件-- <!-- [if supportFields]> PAGEREF _Toc226176915 /h <![endif]-->9<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4接口-- <!-- [if supportFields]> PAGEREF _Toc226176916 /h <![endif]-->10<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.1接口与按照契约(Contract)编程-- <!-- [if supportFields]> PAGEREF _Toc226176917 /h <![endif]-->10<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.2接口与封装-- <!-- [if supportFields]> PAGEREF _Toc226176918 /h <![endif]-->10<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.3nsISupports基接口-- <!-- [if supportFields]> PAGEREF _Toc226176919 /h <![endif]-->11<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.4对象所属关系-- <!-- [if supportFields]> PAGEREF _Toc226176920 /h <![endif]-->12<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.5XPCOM中的指针-- <!-- [if supportFields]> PAGEREF _Toc226176921 /h <![endif]-->12<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.6对象接口的发现-- <!-- [if supportFields]> PAGEREF _Toc226176922 /h <![endif]-->14<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.4.7XPCOM 中的异常-- <!-- [if supportFields]> PAGEREF _Toc226176923 /h <![endif]-->15<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.5XPCOM 的ID- <!-- [if supportFields]> PAGEREF _Toc226176924 /h <![endif]-->16<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.5.1XPCOM ID类-- <!-- [if supportFields]> PAGEREF _Toc226176925 /h <![endif]-->16<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.5.2CID- <!-- [if supportFields]> PAGEREF _Toc226176926 /h <![endif]-->16<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.5.3契约ID- <!-- [if supportFields]> PAGEREF _Toc226176927 /h <![endif]-->16<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.6类厂-- <!-- [if supportFields]> PAGEREF _Toc226176928 /h <![endif]-->17<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.6.1封装构造函数-- <!-- [if supportFields]> PAGEREF _Toc226176929 /h <![endif]-->17<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.6.2XPIDL 与类型库-- <!-- [if supportFields]> PAGEREF _Toc226176930 /h <![endif]-->18<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.7XPCOM 服务-- <!-- [if supportFields]> PAGEREF _Toc226176931 /h <![endif]-->19<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.8XPCOM 类型-- <!-- [if supportFields]> PAGEREF _Toc226176932 /h <![endif]-->19<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.8.1方法类型-- <!-- [if supportFields]> PAGEREF _Toc226176933 /h <![endif]-->19<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.8.2引用计数-- <!-- [if supportFields]> PAGEREF _Toc226176934 /h <![endif]-->20<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.9状态码-- <!-- [if supportFields]> PAGEREF _Toc226176935 /h <![endif]-->20<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.10变量映射-- <!-- [if supportFields]> PAGEREF _Toc226176936 /h <![endif]-->20<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
2.11通用XPCOM 错误码-- <!-- [if supportFields]> PAGEREF _Toc226176937 /h <![endif]-->20<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
3配置XPCOM的编译环境-- <!-- [if supportFields]> PAGEREF _Toc226176938 /h <![endif]-->21<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
3.1Gecko SDK- <!-- [if supportFields]> PAGEREF _Toc226176939 /h <![endif]-->21<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
3.2配置VS编译环境-- <!-- [if supportFields]> PAGEREF _Toc226176940 /h <![endif]-->21<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4创建一个XPCOM工程-- <!-- [if supportFields]> PAGEREF _Toc226176941 /h <![endif]-->23<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.1打开VS选择新建工程弹出如图所示,选择Win32Dynamic-Link Library- <!-- [if supportFields]> PAGEREF _Toc226176942 /h <![endif]-->24<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.2创建一个空的工程如图所示,选择完成-- <!-- [if supportFields]> PAGEREF _Toc226176943 /h <![endif]-->24<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.3为主接口创建GUID- <!-- [if supportFields]> PAGEREF _Toc226176944 /h <![endif]-->25<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.4创建接口定义 文件- IMyComponent.idl <!-- [if supportFields]> PAGEREF _Toc226176945 /h <![endif]-->25<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.5用接口定义文件生成接口头文件和类型库(typelib)文件-- <!-- [if supportFields]> PAGEREF _Toc226176946 /h <![endif]-->25<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.6生成自己的组件实现-- <!-- [if supportFields]> PAGEREF _Toc226176947 /h <![endif]-->26<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.7创建你的组件的头文件MyComponent.h- <!-- [if supportFields]> PAGEREF _Toc226176948 /h <![endif]-->26<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.8创建你的组件的实现文件MyComponent.cpp- <!-- [if supportFields]> PAGEREF _Toc226176949 /h <![endif]-->26<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.9创建你的模块定义文件MyComponentModule.cpp- <!-- [if supportFields]> PAGEREF _Toc226176950 /h <![endif]-->27<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.10将上面创建的文件添加到刚刚创建的空的工程中-- <!-- [if supportFields]> PAGEREF _Toc226176951 /h <![endif]-->28<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
4.11添加方法和属性-- <!-- [if supportFields]> PAGEREF _Toc226176952 /h <![endif]-->28<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
5在C++如何调用XPCOM组件接口-- <!-- [if supportFields]> PAGEREF _Toc226176953 /h <![endif]-->29<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
5.1调用普通组件-- <!-- [if supportFields]> PAGEREF _Toc226176954 /h <![endif]-->29<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
5.2调用服务组件-- <!-- [if supportFields]> PAGEREF _Toc226176955 /h <![endif]-->29<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
6在JS中XPCOM组件接口-- <!-- [if supportFields]> PAGEREF _Toc226176956 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
6.1调用普通组件-- <!-- [if supportFields]> PAGEREF _Toc226176957 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
6.2调用服务组件-- <!-- [if supportFields]> PAGEREF _Toc226176958 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7组件事件-- <!-- [if supportFields]> PAGEREF _Toc226176959 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.1 nsIObserverService接口-- <!-- [if supportFields]> PAGEREF _Toc226176960 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Summary- <!-- [if supportFields]> PAGEREF _Toc226176961 /h <![endif]-->30<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Method overview- <!-- [if supportFields]> PAGEREF _Toc226176962 /h <![endif]-->31<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Methods <!-- [if supportFields]> PAGEREF _Toc226176963 /h <![endif]-->31<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.2 nsIObserver <!-- [if supportFields]> PAGEREF _Toc226176964 /h <![endif]-->33<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Summary- <!-- [if supportFields]> PAGEREF _Toc226176965 /h <![endif]-->33<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Interface Code <!-- [if supportFields]> PAGEREF _Toc226176966 /h <![endif]-->33<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Methods <!-- [if supportFields]> PAGEREF _Toc226176967 /h <![endif]-->33<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Related Interfaces <!-- [if supportFields]> PAGEREF _Toc226176968 /h <![endif]-->34<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
Example Code <!-- [if supportFields]> PAGEREF _Toc226176969 /h <![endif]-->34<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.3如何在JS中注册事件-- <!-- [if supportFields]> PAGEREF _Toc226176970 /h <![endif]-->35<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.3.1创建一个observer对象-- <!-- [if supportFields]> PAGEREF _Toc226176971 /h <![endif]-->35<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.3.1注册Observer <!-- [if supportFields]> PAGEREF _Toc226176972 /h <![endif]-->36<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.4如何在XPCOM中调用事件-- <!-- [if supportFields]> PAGEREF _Toc226176973 /h <![endif]-->36<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.41XPCOM添加observer属性-- <!-- [if supportFields]> PAGEREF _Toc226176974 /h <![endif]-->36<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.4.2在主线程中产生事件-- <!-- [if supportFields]> PAGEREF _Toc226176975 /h <![endif]-->36<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
7.4.3在其他线程中产生事件-- <!-- [if supportFields]> PAGEREF _Toc226176976 /h <![endif]-->37<!-- [if gte mso 9]><![endif]--><!-- [if supportFields]><![endif]-->
<!-- [if supportFields]><![endif]-->
1.1分离界面和实现
我们知道,用户界面是最容易变化的,也是最难于自动测试的,分离用户界面和内部逻辑是设计的主要目标之一。在这一方面,mozilla算是非常前卫了:用标记语言(XUL)开发界面,用编程语言来实现(C++)内部逻辑,再用脚本语言(javascript)把两者胶合起来。XUL的界面描述能力,javascript的简洁性和C++的性能完美的结合在一起了,mozilla把三者的长处发挥到了极致。它们的关系如下图所示:
<!-- [if gte vml 1]> <![endif]-->
o XUL 这是一种用XML来描述用户界面的语言。用XML描述用户界面已经不是什么新鲜事了,像Qt designer和Glade都是用XML文件格式来存放用户界面描述的,但它们都只是纯粹的界面描述。而XUL同时描述了事件处理、风格(style) 和字符串国际化等信息,可以直接被mozilla layout引擎解析执行。
o XBL 这种称为扩展绑定语言(Extensible Binding Language)的东东也是mozilla的一大特色,现在已经被W3C作为标准了。作为程序员,我们都知道公共函数库的重要性,公共函数库可以反复重用,从而提高开发效率。在开发用户界面时,也会遇到同样的问题,很多界面都比较类似,拷贝/粘贴当然很容易,但以后维护起来就麻烦了。而XUL并没有提供重用机制,XBL刚好弥补了它的不足。在XUL中可以只描述具有共性的部分,而由XBL对它进行扩展。XBL的功能强大,自身也有组合和继承机制,这大大提高了可重用性。
o CSS 我们知道Cascading Style Sheets在网页中已经应用多年了,而在浏览器本身实现中使用倒是很少听说。这也没有什么奇怪的,像GTK+中的RC和CSS功能都差不多,也就是说GNOME应用程序一直都在使用类似于CSS的东西。有了CSS,把应用程序的界面视感(look and feel)与功能独立开来,让两者可以独立变化,这是非常自然的事了。不过CSS在这里,除了可以修改界面风格外,还可以把XBL和XUL关连起来,以完成对XUL的扩展。
o DTD DTD (Document Type Definition)常用来定义标记(Markup Langugae)语言的语法,功能上与BNF是等价的。不过它在这里,不是为了定义某种语言的语法,而是完成字符串的本地化,只是借了DTD中的实体(Entity)展开机制罢了。这看起来有些大材小用,不过在XML中使用DTD实体来替换要翻译的字符串,没有比这更好的办法了。
o property 在XUL中用DTD来做字符串本地化,虽然是妙着一招,可是在javascript里它就没有用武之地了。这回该轮到property上场了,在 nsIStringBundle接口的帮助下,javascript可以方便的从property文件中取到所要的字符串。
o Javascript 在XPConnect的支持下,Javascript也可以用来开发COM组件,可以实现任何功能。不过胶合用户界面(XUL)和内部逻辑才是它最拿手的好戏。当然,其中文档对象模型(DOM)起了非常关键的作用,Javascript通过文档对象模型(DOM)来操作XUL中的元素。
Mozilla整个设计是基于组件对象模型(COM)的,而组件对象模型(COM)的主要特点就是针对接口编程。在《设计模式》中,作者把针对接口编程作为设计的首要准则。针对接口编程,把模块的使用者和实现者之间的耦合降到最小,以达到信息隐藏和隔离变化的目的。而且在XPCOM的帮助下,组件可以动态替换或者增加,具有更强的灵活性。其中各个角色的关系如下图所示:
<!-- [if gte vml 1]> <![endif]-->
工厂(Factory) 我们知道,可以通常接口指针调用接口的实现,这样调用者就不必了解实现者的细节了。然而创建接口的实例时,只有知道接口的实现才能创建接口的实例。这是一个矛盾,如何才能把使用者和实现者完全隔离开来呢?那就要靠工厂(Factory),它把接口的实现和创建独立开来了。可以说工厂(Factory)是针对接口编程的基本条件之一,这就是为什么《设计模式》把工厂(Factory)模式放在前面介绍的原因了。
在Mozilla中,每个组件都要实现工厂(Factory)接口,才能被使用者调用。考虑到实现Factory的代码极为类似,为了避免重复,Mozilla实现了一个称为GenericFactory的通用Factory。在GenericFactory的帮助下,各个组件只要实现一个简单的结构ComponentInfo就行了。
组件管理器工厂(Factory)虽然把组件的创建分离了,但使用者与实现者的工厂却又耦合起来了。这样看来,我们似乎又回到了起点,不过这次不一样了,各个组件的 Factory都实现了IFactory接口,可以由一个专门的管理器来管理。通过组件名称或ID可以找到组件的Factory,通过接口ID又可以让Factory创建接口的实例。
IDL IDL是接口定义语言 (Interface Definition Language) 的简称,它在组件对象模型(COM)中也起着重要的作用。在C语言中,我们可以用结构(Struct)来描述接口,一个只包含函数指针的结构 (Struct)可以看作一个接口。在C++中,我们可以用类来描述接口,一个只包含纯虚函数的类可以看作一个接口。既然每种语言都有自己描述接口的方法,为什么还要IDL呢?答案正是因为每种语言描述接口的方式不一致,所以才要需要一个独立于任何编程语言接口定义语言(IDL)。
IDL是一个中间语言,它不能直接被编程语言使用,但在IDL的编译器的支持下,它可以被转换成不同的形式,在多种语言中使用。比如转换成头文件,可以在C/C++中使用,可以转换成类型库,可以在javascript等解释语言中使用。IDL在XPCOM实现语言无关性中,起了不可或缺的作用。
分层设计是常用的架构设计手法之一,它基于分而和治之的思想,可以有效的降低系统复杂度。分层设计同时还可以隔离变化,让上层模块不受到底层模块变化的影响,是开发跨平台软件的最强有力武器。在mozilla中,其分层架构视图如下所示:
<!-- [if gte vml 1]> <![endif]-->
NSPR是一套跨平台的运行库,实现了诸如线程、事件、信号量和文件等依赖于具体操作系统API的抽象,让上层模块不受具体操作系统的影响。其下有基于Linux、Windows和Mac等多种操作系统的实现。
o Gfx 是一套图形处理程序库,实现了诸如画线、多边形、填充、颜色、字体和打印等功能,让上层模块不受具体GUI的影响。其下有基于GTK、QT和Win32等多种GUI的实现。
o Widget 是一套窗口/控件程序库,实现了诸如窗口,事件、拖放、剪切板等功能,让上层模块不受具体GUI的影响。其下有基于GTK、QT和Win32等多种GUI的实现。
o XPCOM 实现了跨平台的组件对象模型(COM),这是mozilla的核心模块。
o JSEngine实现了javascript的解释器,以及与XPCOM组件之间的互相调用机制。
o ML Parser实现了各种标记语言的解析器,比如HTML、XUL、XBL、SVG和其它XML文件解析器。
o DOM 实现了文档对象模型接口,javascript可以通过这套接口操作文档。DOM是W3C定义的标准,支持DOM接口才能保证和其它浏览器的兼容性,让同样的javascript可以在不同的浏览器中运行。
o Network 实现了各种本地/远程协议,比如HTTP、FTP、about和file等协议。Mozilla还实现了SSL协议,保证数据传输的安全性。
o Layout Engine实现了界面的排版布局,支持HTML、XUL和SVG等文档的排版布局。它不但支持网页浏览,而且支持网页编辑。
o 最上层是用XUL和JS等界面元素开发出来的应用程序,像firefox和Thunderbird等。
对于浏览器来说,可扩展性也是非常重要的。由于它涉及的东西太多,专业的功能应该由专业的厂商去做,而不是全部由浏览器来实现。比如像flash播放器、视频播放器和pdf阅读器等等都应该从浏览器中分离出来。Mozilla提供了两种扩展机制,一种称为plugin,另外一种称为 extension。这可能有点让人混淆,我是这样理解的:
o plugin用来增强现有功能,比如PDF plugin可以阅读PDF文件,而media player plugin可以播放音/视频文件。Plugin都要求实现特定的接口。
o extension用来扩展新功能,这些功能可能与浏览器有关,也可能无关,像help extension就是用来实现帮助功能的。Extension不要求实现特定的接口。
Mozilla是一套跨平台的系统,它可以运行在linux、windows和mac等操作系统上,可以运行在QT、GTK和Win32等GUI系统上。可移植性是mozilla是主要目标之一,开发人员在为此可谓费尽苦心:
o 实现了一套跨平台的组件对象模型(XPCOM),这使得mozilla可以利用组件对象模型(COM)的好处,又不局限于windows平台。
o 实现了一套可移植运行库,对各种操作系统的接口作了抽象,隔离上层模块对操作系统的依赖。
o 实现了widget和gfx两个库,前者负责窗口/控件的处理,后者负责图形/图像的绘制,对各种GUI系统作了抽象,隔离了上层模块对GUI的依赖。
o 实现了自己的国际化机制,包括编码转换、编码检测和字符串翻译等等。
用C/C++开发应用程序,最麻烦的就是内存泄露和内存越界,即使有多年开发经验的老手也可能在此栽了跟头。有人说,有很多工具可以帮助检测内存问题啊。没错,但有两种情况可能让这些工具失效,一是GUI系统,它们通常使用了共享内存,大多数工具对此都无能为力。不信的话,你可以用valgrind 检查一下GTK应用程序试试。二是自己管理了内存,大型系统中,为了高效的使用内存,往往实现了自己的内存管理器,工具对此一无所知,自然帮不上忙。
Mozilla实现了自己的内存管理器,同时还实现多种内存问题检查机制,比如用boehm垃圾回收机制来检查内存泄露问题。当然,对于C/C++这个毛病,也是迫不得已,大家都在重复实现这些东西,却没有好的重用办法,这不怪mozilla。
Cross Platform Component ObjectModule (XPCOM) 是一个允许开发人员把一个大的工程划分成小的模块的框架. 这些小模块称为组件, 它们在运行时刻组装在一起.
XPCOM 的目标是使软件的不同部分分别开发, 相互独立. 为了是应用的不同组件之间能够互操作, XPCOM 把组件的实现与接口(后面讨论接口 )分开. 同时 XPCOM 还提供了加载和操纵这些组件的库和工具以及服务, 以帮助开发人员编写跨平台的代码和组件版本管理; 因此组件可以在不破坏应用或者重新生成应用的同时被替换被更新. 通过使用 XPCOM, 开发人员创建的组件可以在不同的应用中被重用, 或者替换当前应用中的组件以改变应用的功能.
XPCOM 不只提供组件软件开发的支持, 它同时提供一个开发平台的大多数功能的支持:
我们将在后面的章节中详细讨论上面的条目, 但是现在, 仅仅把 XPCOM 想象成一个 组件开发平台, 它提供了上面的这些特性.
XPCOM 尽管在许多方面类似于 Microsoft COM, 但 XPCOM 被设计成主要应用于应用层. XPCOM 的最重要的应用是 Gecko, 一个开源的, 遵循标准的, 嵌入式 web 浏览器和 工具包.
XPCOM 是访问 Gecko 库和嵌入或扩展 Gecko 的方法. 本书着重于后者 -扩展 Gecko - 但是本书中描述的基本思想对嵌入Gecko 的开发人员也很重要.
Gecko 应用在许多 internet 程序中, 主要是浏览器. 包括Gateway/AOL Instant AOL device 和 Nokia Media Terminal.Gecko 最近还被应用于的 Compuserve 客户端,AOL for Mac OS X, Netscape 7, 当然还包括 Mozilla 客户端. Gecko 是目前而言主流的开源浏览器.
XPCOM 允许把一个大的工程分解为一些小部分. 这些小部分称为组件, 通常是一些小的可重用的二进制库(在Windows 下表现为一个 DLL , Unix 下为一个 DSO ), 这些二进制库可以包含一个或多个组件. 当多个相关组件被封装到一个二进制库, 这个库也称为模块.
把软件划分成不同的组件可以使开发和维护工作变得更容易. 除了这个好处, 模块化组件化的编程还有下面的优势:
优点 |
描述 |
重用 |
模块化的代码可以在其他应用和环境中被重用. |
更新 |
在不需要重新编译整个应用的情况下更新组件. |
性能 |
代码按照模块化组织, 模块就不必要立刻被加载, 而是以 "lazy" 方式加载, 或者根本不需要加载. 这样就可以提升应用的整体性能. |
维护 |
甚至在不更新组件的情况下, 按照模块化的方式设计应用也能够使你对感兴趣的部分的维护变得更容易. |
Mozilla 有几百万行代码, 没有那个人能够彻底搞明白整个代码的细节. 面对这样的一个系统, 最好的方式是把它划分成一些小的, 更容易管理的部分; 同时使用组件模型来编程, 把相关组件组织到各个模块中去. 比如说, Mozilla 的网络库就包含了 HTTP, FTP, 及其他协议的组件, 这些组件被塞到一个单独的库里. 这个库就是网络模块, 也就是通常所说的 "necko."
但是把事物划分开来并不一定就好, 有许多事物作为一个整体组织起来才是最好的方式, 或者根本就不能划分开来. 比如说, 一个小孩就可能不吃没有果酱的三明志, 对于他来说, 果酱和三明志是缺一不可的整体. 同样的情形也会存在于某些软件中, 某些需要紧耦合的仅仅在内部使用的部分就并不需要费力去把它拆开成为小的模块.
Gecko 中的 HTTP 组件并不会暴露它内部的类, 它是作为一个整体来使用. 组件内部的事物不应该暴露给 XPCOM. 在 Mozilla 早期的开发中, 有些组件的创建并不适当, 但是随着开发的深入, 这些部分会被逐渐移出 XPCOM.
把软件划分成组件通常一个好的解决方法, 但是我们该怎么做呢? 基本的思路是按照功能进行划分, 理解这些功能块之间的如何进行通信. 这些组件之间的通信通道就构成了各个组件的边界, 这些边界形式的描述为接口.
接口并不是编程中的一个新的概念. 从我们的第一个 "HelloWorld" 程序开始我们就一直在不知不觉的使用它, 这里的接口就存在于我们的应用程序和打印程序之间. 应用程序使用 stdio
库提供的接口来把 "hello world" 字符串打印到屏幕上. XPCOM 与 "HelloWorld" 程序的不同之处在于 XPCOM 会在运行时刻找到这个屏幕打印的功能, 而 XPCOM 事前根本就不需要在编译的时刻了解 stdio
库提供了什么东西.
接口允许开发人员把功能的具体实现封装在组件内部, 而客户程序不需要了解这个功能是如何实现的, 它只需要使用它就行了.
一个接口在组件与客户程序之间达成契约. 并没有任何强制措施保证认同这个契约, 但是忽略它会是致命的. 在基于组件的编程中, 组件保证它提供的接口是不变的 - 不同版本的组件都要提供同样的访问方法 - 这就与使用它的客户程序达成了一种契约. 从这种角度来说, 基于组件的编程通常也称为按照契约编程.
组件边界之间的抽象对软件的可维护性与可重用性是致关重要的.举个例子来说, 考虑下面一个没有很好封装的类. 在下面的例子中, 使用可自由访问的 public 的初始化方法可能会出问题.
SomeClass类初始化
class SomeClass
{
public:
// Constructor
SomeClass();
// Virtual Destructor
virtual ~SomeClass();
// init method
void Init();
void DoSomethingUseful();
};
系统要工作正常, 客户程序员必须注意到组件程序员建立的潜规则. 这就是这个未很好封装的类的契约: 这是一套关于何时该调用一个方法, 调用这个方法之前应该做什么的规则. 这个规则可能指出 DoSomethingUseful
方法只能在调用 Init()
之后被使用. DoSomethingUseful
方法可能会做某些检查工作以保证条件满足 - Init
已经被调用.
除了在代码中给出注释告诉客户程序员关于 Init()
规则之外, 程序员可以使他的契约更明晰.首先对象的构造函数可以封装起来, 然后向客户程序员提供一个声明 DoSomethingUseful
方法的虚基类. 通过这种方式, 构造函数和初始化函数被隐藏起来. 在这种半封装条件下, 这个类只向客户程序暴露一些良好定义的可调用方法(或者称为接口). 一旦按照这种方式封装一个类, 客户程序只能看到的是下面的接口:
SomeInterface封装
class SomeInterface
{
public:
virtual void DoSomethingUseful() = 0;
};
实现类就可以从这个虚基类派生, 实现接口的功能. 客户程序使用类厂(factory pattern)模式来创建对象(参看类厂 ), 而封装类的内部实现. XPCOM 以这种方式把客户程序屏蔽在组件的内部工作之外, 而客户程序也只依赖于提供所需要功能的接口.
2.4.3
nsISupports
基接口 组件与基于接口的编程的两个最基本的问题是: 一个是组件生存期, 也称为对象所属关系; 另一个是接口查询, 它是在运行时刻确定接口能够提供哪些接口. 这一节介绍基接口 nsISupports
- 它是 XPCOM 中所有接口的父接口, 它提供了上面两个问题的解决方案.
在 XPCOM 中, 由于组件可以实现任意多的不同接口, 接口必须是引用计数的. 组件在内部跟踪客户程序对它的接口引用了多少次, 当接口计数为零的时候组件就会卸载它自己.
当一个组件创建后, 组件内部有一个整数在跟踪这个引用计数值. 客户程序实例化组件就会自动对这个引用计数加一; 在组件的整个生存期内, 引用计数或加或减, 总是大于零的. 如果在某个时刻, 所有的客户程序对该组件都失去了兴趣, 引用计数减到零, 组件就会卸载自己.
客户程序使用相关接口是一个非常直接的过程. XPCOM 有一些工具让我们更方便的使用接口, 我们会在后面讲述. 如果客户程序在使用接口的时候忘记对接口的引用计数进行相关操作, 就会对组件的维护工作带来某些问题. 此时, 由于组件的引用计数始终不为零, 它就永远不会释放, 从而导致内存泄漏. 引用计数系统就象XPCOM 的许多其他事物一样, 是客户与组件之间的契约. 如果遵守这些契约, 就会工作得很正常, 反之不然. 由创建接口指针的函数负责对初始化的接口引用加1, 这个引用也称为所属引用.
XPCOM 中的指针术语指的是接口指针. 它与常规指针相比有细微的差别, 毕竟它们都指向的是某个内存区域. 但是 XPCOM 指针指向的都是从 nsISupports 基接口派生而来的接口实现, 这个基接口包括三个基本的方法: AddRef
,Release
,和 QueryInterface
.
nsISupports
接口提供了对接口查询与引用计数基本的支持. 这个接口的成员方法包括: QueryInterface
, AddRef
,和 Release
. 这些方法提供了从一个对象获取正确接口的基本方法, 加引用计数, 释放不再使用的对象. nsISupports
接口的声明如下:
nsISupports
接口
class Sample: public nsISupports
{
private:
nsrefcnt mRefCnt;
public:
Sample();
virtual ~Sample();
NS_IMETHOD QueryInterface(const nsIID &aIID, void **aResult);
NS_IMETHOD_(nsrefcnt) AddRef(void);
NS_IMETHOD_(nsrefcnt) Release(void);
};
接口中使用的各种数据类型见XPCOM类型 一节. nsISupports
接口的实现代码如下:
nsISupports
接口实现
Sample::Sample()
{
// initialize the reference count to 0
mRefCnt = 0;
}
Sample::~Sample()
{
}
// typical, generic implementation of QI
NS_IMETHODIMP Sample::QueryInterface(const nsIID &aIID,
void **aResult)
{
if (aResult == NULL) {
return NS_ERROR_NULL_POINTER;
}
*aResult = NULL;
if (aIID.Equals(kISupportsIID)) {
*aResult = (void *) this;
}
if (*aResult == NULL) {
return NS_ERROR_NO_INTERFACE;
}
// add a reference
AddRef();
return NS_OK;
}
NS_IMETHODIMP_(nsrefcnt) Sample::AddRef()
{
return ++mRefCnt;
}
NS_IMETHODIMP_(nsrefcnt) Sample::Release()
{
if (--mRefCnt == 0) {
delete this;
return 0;
}
// optional: return the reference count
return mRefCnt;
}
继承是面向对象编程中另一个非常重要的话题. 继承是通过一个类派生另一个类的方法. 当一个类继承另一个类, 继承类可以重载基类的缺省动作, 而不需要拷贝基类的代码, 从而创建更加专有的类. 如下所示:
简单类继承
class Shape
{
private:
int m_x;
int m_y;
public:
virtual void Draw() = 0;
Shape();
virtual ~Shape();
};
class Circle : public Shape
{
private:
int m_radius;
public:
virtual Draw();
Circle(int x, int y, int radius);
virtual ~Circle();
};
Circle
从 Shape
类派生. Circle
本身也是一个 Shape
, 但是 Shape
并不一定是 Circle
. 在这种情况下, Shape
是基类, Circle
是 Shape
的子类.
在 XPCOM 中, 所有的类都派生自 nsISupports
接口, 这样所有的对象都提供 nsISupports
接口,但是这些对象是更专有的类, 在运行时刻必须能找到它. 比如说在简单类继承 例子中, 如果对象是一个 Circle
, 你就可以调用Shape
类的方法. 就是为什么在 XPCOM 中, 所有的对象都派生自 nsISupports
接口: 它允许客户程序根据需要发现和访问不同的接口.
在 C++ 中, 我们可以使用 dynamic_cast<>
来把一个 Shape
对象的指针强制转化成一个Circle
指针, 如果不能转化就抛出异常. 但是在 XPCOM 中, 由于性能开销和平台兼容性问题, 采用RTTI 的方法是不行的.
XPCOM 并不直接支持 C++ 的异常处理. 在 XPCOM 中, 所有的异常必须在组件内部处理, 而不能跨越接口的边界. 然后接口方法返回一个 nsresult
错误值(这些错误码请参考 XPCOM API Reference).客户程序根据这些错误码进行"异常"处理.
XPCOM 没有采用 C++ RTTI 机制来实现对象指针的动态转化, 它使用 QueryInterface
方法来把一个对象指针 cast 成正确的接口指针.
每个接口使用称为 "uuidgen"的工具来生成专有ID. 这个 ID 是一个全局唯一的 128-bit 值. 在接口的语境中,这个 ID 又称为 IID. (组件语境中, 这个 ID 代表的是一个契约)
当客户程序想查询一个对象是否支持某个接口, 它把接口的 IID 值传递给这个对象的 QueryInterface
方法. 如果对象支持这个接口, 它就会对自己的引用计数加1, 然后返回指向那个专有接口的指针. 反之, 如果不支持这个接口, 它会返回一个错误码.
class nsISupports {
public:
long QueryInterface(const nsIID & uuid,
void **result) = 0;
long AddRef(void) = 0;
long Release(void) = 0;
};
QueryInterface
的第一个参数是一个nsIID
类型的引用, 它封装了 IID. nsIID
类有三种方法: Equals
,Parse
,和 ToString
. Equals
在接口查询中是最重要的, 它用来比较两个 nsIID
对象是否相同.
在客户以 IID 调用 nsISupports
接口的 QueryInterface
方法时, 我们必须保证返回一个有效的 result 参数(在UsingXPCOM Utilities to Make Things Easier 一章中, 我们将看到怎样更方便的实现一个nsIID
类). QueryInterface
方法应该支持该组件所有接口的查询.
在 QueryInterface
方法的实现中, IID 参数与组件支持 nsIID
类进行比较. 如果匹配, 对象的this
指针转化为 void
指针, 引用计数加1, 把void
指针返回给客户程序.
在上面的例子中, 仅仅使用 C 方式的类型转化就足够了. 但是在把 void
指针 cast 成接口指针的时候, 还有更多的问题, 因为返回的接口指针必须与 vtable相对应. 当出现菱形多重继承的时候, 可能这种接口转化就会有问题.
除了上一节中的接口 IID, XPCOM 还使用两种 ID 来区分类与组件.
nsIID
类实际上是一种nsID
类. 其他的nsID
, CID 和 IID,分别指的是一个实体类和一个专有的接口.
nsID
类提供 Equals
类似的方法, 来比较 ID. 请参考Identifiersin XPCOM 一节, 其中对 nsID
类有更多的讨论.
CID 是一个唯一的 128-bit 值, 类似于 IID, 它用于全局标识唯一的类或者组件. nsISupports
的 CID 就象:
00000000-0000-0000-c000-000000000046
由于 CID 比较长, 通常我们都使用#define来定义它:
#define SAMPLE_CID /
{ 0x777f7150, 0x4a2b, 0x4301, /
{ 0xad, 0x10, 0x5e, 0xab, 0x25, 0xb3, 0x22, 0xaa}}
我们将会看到许多 NS_DEFINE_CID
的定义. 下面的宏用 CID 定义了一个静态变量:
static NS_DEFINE_CID(kWebShellCID, NS_WEB_SHELL_CID);
CID 有时也称为类 ID. 如果一个类实现了多个接口, 当CID 在该类发布后, 就与该类的 IID 一起被冻结了.
契约 ID 是一个用于访问组件的可读(to humen)的字符串. 一个CID 或者一个契约 ID 可以用于从一个组件管理器获取组件.下面是一个用于 LDAP 操作组件的契约 ID:
"@mozilla.org/network/ldap-operation;1"
契约 ID 的格式是: 用斜杠分开的组件的域, 模块, 组件名, 版本号.
与 CID 类似, 契约 ID 指的是组件实现而不是接口.但是契约 ID 并不是某个专有实现, 它的用途更广泛. 一个契约 ID 只是指明需要实现的一组接口. 契约 ID 与 CID 的这种不同, 使得组件重载成为可能.
把代码划分成组件, 客户代码通常使用 new
操作符来构造实例对象:
SomeClass* component = new SomeClass();
这种模式或多或少地需要客户对组件有一定的了解. 类厂设计模式此时可以用来封装对象的构造过程. 类厂模式的目的是在不暴露对象的实现前提下创建对象. 在 SomeClass
例子中, 可以按照类厂模式把 SomeClass
对象的构造和初始化封装在 New_SomeInterface
函数中:
int New_SomeInterface(SomeInterface** ret)
{
// create the object
SomeClass* out = new SomeClass();
if (!out) return -1;
// init the object
if (out->Init() == FALSE)
{
delete out;
return -1;
}
// cast to the interface
*ret = static_cast<SomeInterface*>(out);
return 0;
}
类厂本身是一个管理组件实例化的类. 在 XPCOM 中, 类厂要实现 nsIFactory
接口, 它们就象上面的代码一样要使用类厂设计模式来封装对象的构造和初始化.
封装构造函数 的例子是一个简单的无状态的类厂版本, 实际的编程要复杂一些, 一般的类厂都需要保存状态. 类厂至少应该保存那些对象已经被创建了的信息. 如果类厂管理的实例被存放在一个动态联接库中, 还需要知道什么时候可以卸载这个动态联接库. 当类厂保存了这样的信息, 就可以向类厂查询一个对象是否已经被创建.
另一个需要保存的信息是关于单件(singleton). 如果一个类厂已经创建了一个单件类型的类, 后续的创建该单件的函数调用将返回已经创建的对象. 尽管有更好的工具和方式来管理单件(在讨论 nsIServiceManager
会看到), 开发人员可能仍然需要通过这种方式来保证只有一个单件对象被创建.
厂模式可以完全利用函数来做, 状态可以保存在全局变量中; 但是使用类的方式来实现厂模式还有更多的好处. 其一是: 我们可以管理从 nsISupports
接口派生而来的类厂本身的生存期. 当我们试图把多个类厂划分成一组, 然后确定是否能卸载这一组类厂的时候, 这一点非常重要. 另一个好处是: 类厂可以引入其他需要支持的接口. 在我们后面讨论 nsIClassInfo
接口的时候, 我们会看到某些类厂使用这个接口支持信息查询, 诸如这个对象是用什么语言写的, 对象支持的接口等等. 这种派生自 nsISupports
的 "future-proofing" 特性非常关键.
定义接口的简单而强劲的方法是使用接口定义语言(IDL) - 这实际上是在一个跨平台而语言无关开发环境下定义接口的需求. XPCOM 使用的是源自于 CORBA OMG 接口定义语言(IDL)的变体, 称为 XPIDL, 来定义接口,XPIDL 可以定义接口的方法, 属性, 常量, 以及接口继承.
采用 XPIDL 定义接口还存在一些缺陷. 它不支持多继承, 同时 XPIDL 定义的方法名不能相同. - 这与 C++ 语言的成员函数通过参数不同重载有区别, 毕竟接口同时要支持类似于 C 这样的语言.
void FooWithInt(in int x);
void FooWithString(in string x);
void FooWithURI(in nsIURI x);
然而尽管有这些问题, XPIDL 的功能仍然是非常强大的. XPIDL 能自动生成以 .xpt 为后缀的类型库, 或者说typelibs. 类型库是接口的二进制表示, 它向非 C++语言提供接口的访问与控制. 非 C++ 语言通过类型库来了解接口支持哪些方法, 如何调用这些方法, 这称为 XPConnect. XPConnect 是 XPCOM 向类似于 JavaScript 这样的语言提供组件访问的 Wrapper. 参看Connectingto Components from the Interface获取更多关于 XPConnect 的信息.
从其他类型的语言访问接口, 常常说成是接口被反射(reflected)到这种语言. 每一个被反射的接口必须提供相应的类型库. 当前可以使用 C, C++, 和JavaScript 来编写组件.
使用其他语言编写组件
在使用其他语言创建组件的时候, 不需要利用 XPCOM 提供给 C++ 程序员的工具(诸如一些宏, 智能指针等等, 我们可以方便的利用到这种语言本身来创建组件. 比如说, 基于 Python 的 XPCOM 组件可以从 JavaScript 来调用.
参看 Resources获取更多使用其他语言来创建组件的信息.
所有的 XPCOM 接口都用 XPIDL 语法来定义. xpidl 编译器会从这个 IDL 文件产生类型库和 C++ 头文件. 在Definingthe WebLock Interface in XPIDL 一节详细描述了 XPIDL 语法.
当客户需要某个组件提供的功能的时候, 通常都是实例化一个新的组件对象. 比如说, 客户程序需要某些处理文件,这里每个组件就代表一个文件, 客户可能会同时处理多个这样的组件.
但是在某些情况下对象表示的是一种服务, 这种服务只能有一个拷贝(尽管会有多个服务同时运行). 每次客户程序访问服务提供的功能时, 客户程序都是与同一个服务实例在打交道. 比如说, 一个用户查询公司的电话号码数据库, 数据库作为一个对象对用户来说都是同一的. 如若不然的话, 就需要维护两个数据库拷贝, 这种开销将非常大, 而且还存在数据内容不一致的问题. 单件设计模式就是提供系统中需要的这种单点访问功能.
XPCOM 不仅提供了对组件的支持和管理服务, 它还包含了许多编写跨平台组件所需要的其他服务. 其中包括: 跨平台文件抽象, 向 XPCOM 开发人员提供同一而强大的文件访问功能. 目录服务, 提供应用和特定系统定位信息. 内存管理, 保证所有对象使用同样的内存分配器. 事件通知机制, 允许传递简单消息. 本教程将在后面展现如何使用这些服务, XPCOMAPI Reference 一节有完整的接口索引列表.
XPCOM 声明了许多数据类型和简单宏, 这些东西将在我们后面的例子中看到. 大多数的宏都是简单的重定义, 下一节我们会描述一些最常用的数据类型.
下面的类型用在 XPCOM 方法调用的参数和返回值定义中.
|
声明方法返回值. XPCOM 的方法缺省的返回值声明. |
|
方法实现返回值. XPCOM 方法函数返回的时候缺省采用这种类型的返回值. |
|
特定类型的方法实现返回值. 诸如 |
|
共享库内部使用的符号局部声明 |
|
共享库导出的符号声明. |
下面的宏提供对引用计数的基本操作.
|
调用 |
|
与上一个方法类似, 不同之处在于这个宏会检查 |
|
调用 |
|
与上一个方法类似, 不同之处在于这个宏会检查 |
下面的宏测试状态码.
|
如果传递的状态码失效, 则返回真. |
|
如果传递的状态码有效, 则返回真. |
|
缺省的引用计数类型, 是一个 32-bit 整数. |
|
缺省数据类型, 是一个 32-bit 整数. |
|
缺省 null 类型. |
|
如果实例未初试化, 返回该值. |
|
如果实例已初试化, 返回该值. |
|
如果方法未实现, 返回该值. |
|
如果组件不支持某种类型接口, 返回该值. |
|
如果指针指向 |
|
如果某个方法失效, 返回该值, 这时一个通用的错误值. |
|
如果一个未预料的错误发生, 返回该值. |
|
如果无法进行内存分配, 返回该值. |
|
如果一个请求的类型未注册, 返回该值. |
首先从这里下载Gecko SDK选择您需要的版本,您下载了这个SDK后,可以解压到任何您需要的目录下,比如你可以解压到c:/gecko-sdk/.
SDK
说明:
gecko-sdk/bin目录:
这个文件夹下面包含了几个重要的文件:
xpidl.exe:这是idl编译器,用以根据idl产生c++头文件或组件类型库文件。
Regxpcom.exe:这是组件注册工作,等我们的xpcom组件制作完以后就会使用它来注册,
如果我们在MOZILLA浏览器中调用组件,其实不会用该工具。
Xpt-dump.exe:类型库查看程序,用来查看.xp文件中的组件信息。
gecko-sdk/include目录:包含了制作XPCOM时所需要的基本的C++头文件。
gecko-sdk/idl目录:包含了idl数据类型定义文件。
值得一提的是光有这个SDK还不行,还需要两个dll文件,下载wintools.zip,从buildtools/windows/bin/x86里拷贝libIDL-0.6.dll、glib-1.2.dll到/gecko-sdk/bin下。否则运行xpidl会报错。
1. 启动VS后首先创建一个win32项目,把”gecko-sdk/include”加到Additional Include Directories里 。
<!-- [if gte vml 1]> <![endif]-->
2. 添加” XPCOM_GLUE,MOZILLA_STRICT_API″宏。
<!-- [if gte vml 1]> <![endif]-->
3. 把 “/gecko-sdk/lib”加到Additional Library Directories里。
<!-- [if gte vml 1]> <![endif]-->
4. 添加”/gecko-sdk/idl”加到Excutabel-file里。
<!-- [if gte vml 1]> <![endif]-->
5. 添加”nspr4.lib xpcom.libxpcomglue_s.lib”库。
<!-- [if gte vml 1]> <![endif]-->
下面一步一步创建一个简单的XPCOM组件工程,并实现一些简单的功能。
<!-- [if gte vml 1]> <![endif]-->
<!-- [if gte vml 1]> <![endif]-->
a. Windows下用guidgen (Visual Studio2003/2005 工具->创建GUID)
b. Linux 下用uuidgen
a. 使用以下模板,添加方法和属性给接口
b. 填入刚生成的GUID
#include "nsISupports.idl"
[scriptable, uuid(你刚生成的GUID)]
interface IMyComponent : nsISupports
{
long Add(in long a, in long b);
};
a. 使用Gecko SDK中的xpidl 工具. xpidl在Gecko SDK主目录的bin/下
b. 将下列命令中的_DIR_替换为Gecko SDK主目录下的idl目录路径
c. xpidl -m header -I_DIR_ IMyComponent.idl 将会创建 IMyComponent.h
d. xpidl -m typelib -I_DIR_ IMyComponent.idl 将会创建 IMyComponent.xpt
e. 注意运行xpidl需要glib-1.2.dll,libIDL-0.6.dll,这些库包含在wintools.zip 中,解压后在gecko_wintools/buildtools/windows/bin/x86下。
f. 生成的IMyComponent.h,IMyComponent.xpt在当前目录下。
接口头文件IMyComponent.h中包含了模板,用于构建你自己组件头和实现文件。你可以拷贝粘帖这些模板,只要修改组件名就可以了。
a. 首先加入
#ifndef _MY_COMPONENT_H_
#define _MY_COMPONENT_H_
...
#endif
b. 加入 #include"IMyComponent.h" 以包含接口的定义。
c. 为组件创建一个GUID. 注意是这种格式:{ 0x9d8eb88b, 0x30ce, 0x46e0, { 0x81,0xb2, 0xca, 0xb1, 0x5a, 0xc6,0xba, 0xc7 } }
d. 加入下列代码,它们主要用于定义你的组件名,contract ID和GUID
#define MY_COMPONENT_CONTRACTID"@mydomain.com/XPCOMSample/MyComponent;1"
#define MY_COMPONENT_CLASSNAME "A Simple XPCOM Sample"
#define MY_COMPONENT_CID 你组件的GUID
e. 从IMyComponet.h上拷贝头文件的模板(以/* Header file */开始,到 /* Implementation file */结束)
f. 用你的组件名替换所有的_MYCLASS_
a. 加入#include"MyComponent.h"以包含你自己的组件定义。
b. 从IMyComponent.h中拷贝实现模板(以 /* Implementation file */开头到#endif)。
c. 用你的组件名替换所有的_MY_CLASS_。
d. 加入实现代码。
a. 添加#include"nsIGenericFactory.h" 用以包含Mozilla GenericFactory的定义。
b. 添加#include"MyComponent.h" 以包含你的组件定义。
c. 添加NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent),以定义你组件的构造函数。注意:MyComponent是你的组件名。
d. 添加。
static nsModuleComponentInfocomponents[] =
{
{
MY_COMPONENT_CLASSNAME,
MY_COMPONENT_CID,
MY_COMPONENT_CONTRACTID,
MyComponentConstructor, //注意将MyComponent替换为你的组件名。
}
};
以定义类名,contract ID和你组件的GUID.
e. 添加 NS_IMPL_NSGETMODULE("MyComponentsModule",components)以输出上述所有信息到Mozilla.
一个基本XPCOM开发框架已经建好了,
<!-- [if gte vml 1]> <![endif]-->
打开IMycomponent.idl文件,手动添加方法和属性如图所示。
<!-- [if gte vml 1]> <![endif]-->
重新编译IMycomponent.idl文件。
nsresult rv;
nsCOMPtr<nsISample> sample =
do_CreateInstance(“@mozilla.org/sample;1”, &rv);
if (NS_FAILED(rv)) return rv;
nsresult rv;
nsCOMPtr<nsIServiceManager> servMgr;
nsCOMPtr<nsIObserverService> obs;
rv = NS_GetServiceManager(getter_AddRefs(servMgr) );
if( NS_FAILED( rv ))
{
returnNS_OK;
}
else
{
rv=servMgr->GetServiceByContractID("@mozilla.org/observer-service;1",
NS_GET_IID(nsIObserverService ), getter_AddRefs( obs ) );
if(NS_FAILED( rv ))
{
returnNS_OK;
}
else
{
rv= obs->NotifyObservers(m_pObserver, "xpcom2JS",(PRUnichar*)"testdata");
if(NS_FAILED( rv ))
{
returnNS_OK;
}
}
}
Var my_xpcom;
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
const cid = "@mydomain.com/XPCOMSample/MyComponent;1";
my_xpcom =Components.classes[cid].createInstance();
my_xpcom =my_xpcom.QueryInterface(Components.interfaces.IMyComponent);
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var observerService =
Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this,"xpcom2JS", false);
在XPCOM中组件的事件机制是和COM一样的,需要在组件实现一个事件的代理,记录注册事件的注册事件。XPCOM采用了一个全局的服务组件来完成这个事情,通过Firefox的源代码我们可以了解到服务器组件都一个单件。
The nsIObserverService
interfaceprovides methods to add, remove, notify, and enumerate observers of variousnotifications. nsIObserverService
is defined in xpcom/ds/nsIObserverService.idl
. It is scriptableand has been frozensince Mozilla 0.9.6 . See bug99163 for details.
#include "nsIObserverService.h"
[scriptable, uuid=(D07F5192-E3D1-11d2-8ACD-00105A1B8860)]
interface nsIObserverService : nsISupports { ... };
The XPCOM nsObserverService
implements this interface to provide globalnotifications for a variety of subsystems.
|
|
|
|
Registers a given listener for anotifications regarding the specified topic.
void addObserver( in nsIObserver anObserver,
in string aTopic,
in boolean ownsWeak);
anObserver
The interface pointer which will receivenotifications.
aTopic
The notification topic or subject.
ownsWeak
If set to false
, the nsIObserverService
will hold a strong reference to anObserver
. If set to true
and anObserver
supports the nsIWeakReference
interface, a weak reference willbe held. Otherwise an error will be returned.
Unregisters a given listener fromnotifications regarding the specified topic.
void removeObserver( in nsIObserver anObserver,
in string aTopic );
anObserver
The interface pointer which will stopreceiving notifications.
aTopic
The notification topic or subject.
Notifies all registered listenersof the given topic.
void notifyObservers( in nsISupports aSubject,
in string aTopic,
in wstring someData );
aSubject
Notification specific interface pointer.
aTopic
The notification topic or subject.
someData
Notification specific wide string.
Returns an enumeration of allregistered listeners.
nsISimpleEnumerator enumerateObservers( in string aTopic );
aTopic
The notification topic or subject.
Returns an enumeration of allregistered listeners.
nsIObserver
is implemented by an object that wishes toobserve notifications. These notifications are often, though not always,broadcast via the nsIObserverService.
nsIObserver
is defined in xpcom/ds/nsIObserver.idl
. It is scriptableand has been frozensince Mozilla 0.9.6 .
[scriptable, uuid(DB242E01-E4D9-11d2-9DDE-000064657374)]
interface nsIObserver : nsISupports {
void observe( in nsISupports aSubject,
in string
aTopic,
in wstring
aData );
};
void observe( in nsISupports aSubject,
in string
aTopic,
in wstring
aData );
observe
will be called when there is anotification for the topic that the observer has been registered for.
In general, aSubject
reflects the object whose change or action is being observed, aTopic
indicates the specific change or action, and aData
is an optional parameteror other auxiliary data further describing the change or action.
The specific values and meaningsof the parameters provided varies widely, though, according to where theobserver was registered, and what topic is being observed.
A single nsIObserver
implementation can observe multiple types of notification, and is responsiblefor dispatching its own behaviour on the basis of the parameters for a givencallback. In general, aTopic
is the primary criterion for such dispatch; nsIObserver
implementations should take care that they can handle being called with unknownvalues for aTopic
.
While some observer-registrationsystems may make this safe in specific contexts, it is generally recommendedthat observe
implementations not add or remove observers while they are being notified.
nsIObserverService
The following code is animplementation of nsIObserver
that is registered to receive notificationsfor the "myTopicID" topic. See GlobalNotifications at XULPlanet for a list of built-in topics possible.
Observing preferences worksslightly differently. See Codesnippets:Preferences - Using preference observers for an example.
function myObserver()
{
this.register();
}
myObserver.prototype = {
observe: function(subject, topic, data) {
// Do your stuff here.
},
register: function() {
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "myTopicID", false);
},
unregister: function() {
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "myTopicID");
}
}
Instantiation - this should befired once you're ready to start observing (ex: a window's load event).
observer = new myObserver();
Destruction - this should be firedonce you're done observing (ex: a window's unload event). Failure to do so mayresult in memory leaks.
observer.unregister();
function myObserver()
{
this.register();
}
myObserver.prototype =
{
observe: function(subject, topic, data)
{
if(topic=="xpcom2JS")
{
alert('Performing3+4. Returned ' + data + '.');
}
},
register: function()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var observerService =Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "xpcom2JS", false);
},
unregister: function()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var observerService =Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "xpcom2JS");
}
}
var my_observer = null;
var my_xpcom = null;
function RegisterEvent()
{
my_observer= new myObserver();
try
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
constcid = "@mydomain.com/XPCOMSample/MyComponent;1";
my_xpcom= Components.classes[cid].createInstance();
my_xpcom= my_xpcom.QueryInterface(Components.interfaces.IMyComponent);
my_xpcom.observer=my_observer;
}
catch (err)
{
alert(err);
return;
}
}
attributensISupports observer;
在JS中给属性赋值
my_xpcom.observer=my_observer;
nsresultrv;
nsCOMPtr<nsIServiceManager>servMgr;
nsCOMPtr<nsIObserverService>obs;
rv= NS_GetServiceManager( getter_AddRefs(servMgr) );
if(NS_FAILED( rv ))
{
returnNS_OK;
}
else
{
rv=servMgr->GetServiceByContractID("@mozilla.org/observer-service;1",
NS_GET_IID(nsIObserverService ), getter_AddRefs( obs ) );
if(NS_FAILED( rv ))
{
returnNS_OK;
}
else
{
rv= obs->NotifyObservers(m_pObserver, "xpcom2JS",(PRUnichar*)"testdata");
if(NS_FAILED( rv ))
{
returnNS_OK;
}
}
}
在其他线程里产生事件需要通过代理的方式才可以,而且在代理必须调用的是XPCOM组件的方法。
首先添加一个组件的接口方法 voidNotifyObservers(in string topic, in string data);
然后通过代理调用这个接口。