XPCOM组件开发

[url]http://www.cnblogs.com/phinecos/archive/2008/05/29/1210298.html[/url]《XPCOM组件开发》笔记(一)
本书是关于Gecko和基于Gecko应用程序来开发XPCOM组件的。简介部分探讨组件的概念,第一章你将编译简单的代码并注册到Mozilla中,此时会探讨组件和模块之间的关系,XPCOM接口以及注册的过程。

假定读者熟悉C++中的继承和封装,很多例子是javascript的,它用来做完脚本对象在Mozilla中访问XPCOM组件,因此熟悉它也是很好的。

XPCOM表示跨平台组件对象模型,类似与微软的COM.若你有这方面的经验,可以直接运用上来。

全书贯穿一个叫WebLock的例子来讲解XPCOM组件的开发,步骤如下:

1) 为组件开发基本的模块代码

2) 使用C++宏,特别的字符串类和智能指针来优化代码

3) 定义组件的功能,为这些功能创建XPIDL接口,为WebLock组件接口开发特定的实现代码。

4) 结束实现WebLock组件:nsIContentPolicy,文件I/O,加锁等

5) 为WebLock组件创建用户接口

6) 打包WebLock以便发布和安装。

组件在运行时组装到一起,构建时和其他组件无关,为了允许一个应用程序中的组件之间的互操作,XPCOM将组件的接口和实现相分离。XPCOM提供了一些工具和库来加载和操作这些组件,一些服务来帮助开发者编写模块化的跨平台代码,以及版本支持,因此组件可以在不破坏应用程序或重新创建应用程序的情况下替换和升级,还具有良好的可复用性。

提供的其他功能有:组件管理,文件抽象化,对象消息传递,内存管理。组件一般以可复用的二进制库形式发布(例如windows上的DLL),它可以包括一个或多个组件,若在一个二进制库中有两个以上相关的组件,这个库就叫做模块。

接口就是组件的通信通道,它不是什么新概念,我们写”hello world”时就用到了接口,那时接口是我们写的应用程序代码和打印代码之间,我们用stdio这个接口来打印字符串。XPCOM来写”hello world”的话,唯一的区别是它是在运行时查找这个打印函数,但在编译时并不知道stdio。

组件和面向接口编程的两个基本问题是组件生命周期和接口查询(在运行时识别组件支持哪些接口)。nsISupports基接口是XPCOM中所有接口的祖先接口,它提供了这两个问题的解决方案。

XPCOM中,接口是引用计数的。组件必须跟踪客户端引用它的数目,并且当引用数为0时删除自己。

当一个组件被创建后,其内部的一个整数跟踪这个引用数。当客户端实例化组件时这个引用计数自动加1,在组件的整个生命周期中,这个引用计数加加减减,始终保持大于0,某个时候,所有的客户都对组件不感兴趣了,引用计数到达0,组件就删除自己。

若由客户来负责接口的计数,那么如果它忘记对引用计数减1的话就会出问题,接口将不会被释放,进而导致内存泄露。nsISupports提供了接口发现和引用计数的基本功能。其成员函数QueryInterface,AddRef,Release,提供了从一个对象获取指定接口,增加引用计数,当对象不再被使用时释放它的功能。


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);
};

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();//引用计数加1
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;
}

XPCOM没有使用C++的RTTI,它通过QueryInterface接口来将对象转换到其支持的接口。每个接口都被赋予一个标识符,它通过一个“uuidgen”的工具产生。UUID是一个唯一的,128位的数。在接口中,它叫做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的简单封装。它有三个方法,Equals,Parse,ToString. Equals最重要,它用来在接口查询的过程中比较两个nsIID。

当你实现nsIID类时,当客户使用nsISupports IID调用QueryInterface时,必须确保其方法返回一个有效值

前面的例子中可以简单地使用c语言风格的转换,但要转换为所需要的类型时要复杂些,因为你必须返回与所需要的接口对应的虚函数表的接口指针。当继承层次中有二义性时会出问题。

除了前面讨论的IID接口标识符外,XPCOM还使用了两种标识符来区别类和组件:1)CID.2)契约ID.

CID和IID类似,也是128位数,唯一标识一个类或组件。用于nsISupports的

CID:00000000-0000-0000-c000-000000000046



#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有时候也叫做类标识符,若CID引用的类实现2个以上的接口,则CID确保当它发布或冻结时实现了接口集合的全部接口。

契约ID是给人看的,CID或者契约ID可以用来从组件管理器那获取一个组件,"@mozilla.org/network/ldap-operation;1",格式是域名/模块/组件名/版本号

和CID一样,契约ID引用到一个实现而不是接口,引用到接口是IID干的。但契约ID并不绑死在一个特定的实现,而CID却绑死了。契约ID只是指明了它想实现的一个特定的接口集合,而任何数目的CID都可以进来满足这个需求。契约ID和CID的这个区别使得它可以用来override组件。

工厂模式可以用来封装对象的创建。工厂的目标是在不向客户暴露对象实现和初始化的情况下创建对象。


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(out);
return 0;
}

XPCOM中,工厂是 nsIFactory接口的实现,使用了工厂设计模式来封装对象的构建和初始化过程。

你可能感兴趣的:(XPCOM组件开发)