创建XPCOM组件 2. 概述

前言和前部分概述地址:

http://blog.csdn.net/xwjbs/article/details/4385445

http://blog.csdn.net/xwjbs/article/details/4388821

 

2. 概述

原文:https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM

接口

接口和封装

基接口nsISupports

    在组件和接口编程中的两个基本问题是组件生存期(也叫做对象所有关系)和接口查询(在运行时辨识组件支持的接口)。本节介绍基接口nsISupports,它是XPCOM中所有接口的老妈,为XPCOM开发者提供了这两个问题的解决方案。

对象所有关系

    在XPCOM中,由于组件可以实现任意数目的不同接口,因此,它必需被引用计数。当组件被创建时,其内部的一个整数将追踪拥有该组件的接口的客户端的数目(也叫引用数)。当客户实例化组件时,引用数将自动增长;在组件生命期,引用数会增加或降低。当所有的客户失去组件的接口,引用数变成0,组件就会删除自身。

    如果客户负责地使用接口,这将是一个非常直白的过程。XPCOM有几个工具,可以让它更加容易,如后所述。当客户使用接口却又忘记减少引用数时,就会发生一些看家问题。此时,接口可能永远不被释放,并导致内存泄露。和XPCOM中许多事物类似,引用计数系统是客户和实现之间的一个合约。人们遵从它,它就生效,不遵从它,就发生错误。创建接口指针,给计数增加初始引用或所有引用(owning reference)是函数的责任。

XPCOM中的指针

    在XPCOM中,指针指的是接口指针。既然接口指针和常规指针都只是内存中的地址,它们的区别微乎其微。不过我们知道,接口指针实现了基接口nsISupports,可以用来调用AddRef, Release, 或QueryInterface等方法。

    nsISupports,如下所示,提供了处理接口发现和引用计数的基本功能。它的成员QueryInterface、AddRef、Release,提供了一种基本的方式,分别可以从一个对象中获得正确的接口、增加引用计数、释放对象。

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接口的实现

    略...

对象接口发现

    继承是OO变成中另一个非常重要的主题。

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。

    在XPCOM中,所有的类都派生自nsISupports接口,因此所有的对象都是nsISupports,同时,也是其它更加详细的类别。你需要在运行时找出这些类别。在上面的例子中,你可能想要询问Shape是否是一个Circle,是否能够像Circle一样使用它。在XPCOM中,这正是nsISupports接口的QueryInterface的特性所在:它允许客户根据所需查找并访问不同的接口。

    在C++中,可以使用更高级的特性,dynamic_cast<>。如果Shape对象无法转换为Circle,它将抛出一个异常。但异常和RTTI并不是一个好的选择,因为它们的开销和许多平台上的兼容性问题。XPCOM使用不同的方式处理问题。

XPCOM中的异常

    XPCOM并不直接支持C++异常。相反地,在跨接口边界之前,所有的异常必需用一个指定的组件处理。在XPCOM中,所有的接口方法应当返回一个nsresult错误值(参考XPCOM API Reference)。这些错误码结果将成为XPCOM处理的“异常”。

    XPCOM使用QueryInterface方法将对象转换到正确的接口(如果该接口被支持的话),而不是使用C++RTTI。

    每个接口都被指定一个标识,该标识由一个叫做“uuidgen”的常用工具生成。UUID(universally unique identifier)是一个唯一的128位数字。用在接口环境中,被叫做IID(相比于组件,它用作公约ID)。

    当客户想要知道一个对象是否支持指定的接口,可以传递接口的IID给对象的QueryInterface方法。如果对象支持所请求的接口,它将增加自身的引用,并传回接口的指针。如果对象不支持所请求的接口,将返回一个错误。

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。

    在实现nsISupports类时,你必需确保类方法在客户端调用QueryInterface(传入nsISupports IID)时返回有效的结果(在使用XPCOM工具一章,可以看到宏如何让这一过程变得容易)。QueryInterface应当支持组件所支持的所有接口。

    在QueryInterface的实现中,IID参数针对nsIID类进行检查。如果有匹配,则转换对象的this指针到void,并增加引用数,然后将接口返回给调用者。如果没有匹配,返回错误,并设置输出值为null。

    在上面的例子中,使用C样式的转换相当简单,但在转换void到请求类型时会比较紊乱,因为你必需返回vtable中和所请求的接口对应的接口指针。当存在二义性继承时,转换就成为一个问题。

XPCOM标识

    除了前节讨论的IID接口标识,XPCOM还使用另外两种非常重要的标识来区分类和组件。

XPCOM标识类

    nsIID类其实是nsID类的一个类型重定义。其它nsID的类型重定义,CID和IID,对应类实现和接口实现。

CID

    nsISupports的CID看起来如下:

00000000-0000-0000-c000-000000000046

    CID的长度导致在代码中处理它们比较笨重繁琐,因此,你经常可以看到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有时也被当作类标识。如果CID标识的类实现了一个以上的接口,则CID保证在发布或冻结时,类实现了整套接口。

公约ID

    公约ID是一个人类可读的字符串,用于访问一个组件。可以使用CID或公约ID从组件管理器中获取组件。下面这个是LDAP操作组件的公约ID:

"@mozilla.org/network/ldap-operation;1"

    公约ID的格式是:组件域、模块、组件名称和版本号,用左斜杠分隔。

    和CID类似,公约ID表示一个实现而不是接口(这是IID的事情)。但公约ID并没有向CID那样和某个特定的实现绑定。相反地,公约ID仅指定一套想要被实现的接口;在其中可包含任意数量的不同的CID,返回请求。公约ID和CID的不同让组件覆盖成为可能。

工厂

    代码分散到组件中,客户端代码通常使用new操作符实例化对象:

SomeClass* component = new SomeClass();

    不过,这种格式需要客户端知道组件的一些信息,至少是多大。可以使用工厂设计模式来封装对象的创建。工厂的目的是创建对象而无需向客户暴露对象的实现和初始化。在SomeClass的实例中,SomeClass的构造和初始化(实现SomeInterface抽象类)被包含在New_SomeInterface函数中,该函数遵循工厂设计模式:

int New_SomeInterface(SomeInterface** ret)
{
    // 创建对象
    SomeClass* out = new SomeClass();
    if (!out) return -1;

     // 初始化对象
    if (out->Init() == FALSE)
    {
        delete out;
        return -1;
    }

     // 转换到接口
    *ret = static_cast<SomeInterface*>(out);
    return 0;
}

    工厂其实是管理各个组件实例创建的类。在XPCOM中,工厂被实现为nsIFactory接口,它们像上面示例中一般,使用工厂设计模式来抽象和封装对象的构造和初始化。

    上的示例是一个简单无状态版本的工厂,在实际编程中通常不会这么简单,通常需要存储状态。至少,工厂需要保存已创建对象的信息。举个例子,当工厂管理建构于动态共享库的类的实例时,它需要知道何时可以卸载共享库。在工厂存储状态时,你可以询问是否有未完成的引用并找出是否已创建对象。

    工厂能够保存的另外一个状态是对象是否为单态。比如,如果工厂创建了一个被认为是单态的对象,那么后续对该对象的创建调用应当返回同一个对象。虽然有工具和更好的方式处理单态(我们将在讨论nsIServiceManager时探讨),但开发者可能想使用该信息来确保,无论调用者做什么,都只能有一个单态对象存在。

    工厂类的需求能够以严格功能的方式进行处理,而状态存储于全局变量;但对工厂使用类有一些好处。比如,当使用类实现工厂的功能时,你可以从nsISupports接口派生,这允许你管理工厂对象自身的生命期。当你想要对工厂集进行分组并检查它们是否能够被卸载时,这非常有用。使用nsISupports接口的另一个好处是你可以支持其它接口。在讨论nsIClassInfo时,我们将看到,一些工厂支持查询底层实现相关的信息,比如用何种语言编写、对象支持哪些接口等。这种“适应未来发展”是派生自nsISupports带来的一个关键优势。

XPIDL和类型库

    定义接口的一种简单且强大的方式(实际上是定义跨平台的语言中性开发环境的接口的需求),是使用IDL(接口定义语言)。XPCOM使用CORBA OMG IDL的变种,叫做XPIDL,它允许你指定方法、属性和常量,还允许定义接口继承。

    使用XPIDL定义接口有几个缺点。它不支持多重继承,并且接口中方法的名称必需是唯一的(即,多个方法的名称不能相同,即使它们拥有不同的参数):

void FooWithInt(in int x);
void FooWithString(in string x);
void FooWithURI(in nsIURI x);

    但是,和使用XPIDL所获得的功能相比,这些缺点完全不是问题。XPIDL允许生成类型库,它们的文件后缀为.xpt。类型库是一个或多个接口的二进制表示。它提供接口的编程控制和访问,这是非C++环境使用接口的关键。当从其它语言访问组件,和在XPCOM中访问类似,使用二进制类型库访问接口、获取接口所支持的方法,并调用方法。XPCOM的这点叫做XPConnect,它是为诸如js之类的语言提供XPCOM访问的中间层。XPConnect的更多信息参考从接口连接组件

    当组件可用C++以外的语言(如js)访问时,我们就称接口“被反射”到了该语言。每个被反射的接口必需拥有一个对应的类型库。目前,你可以用C、C++或js(有时是Python或Java,依赖于相应的绑定状态)编写组件;对使用Ruby和perl构建XPCOM绑定的努力也正在进行。

用其它语言编写组件

    用其它语言创建组件时,虽然无法访问XPCOM为C++开发者提供的一些工具(如宏、模板、智能指针等),但你对所使用的语言本身应当非常熟悉,因此你可以避开C++构建。比如,基于Python的XPCOM组件也可以用在js中,反之亦然。

    关于Python和XPCOM中支持的其它语言的更多信息参考附录B - 资源

    XPCOM中的所有公开接口被使用XPIDL语法定义。类型库和C++头文件就从这些IDL文件生成(生成所使用的工具被叫做xpidl编译器)。节开始WebLock/在XPIDL中定义WebLock接口细描述了XPIDL的语法。

XPCOM服务

    在客户使用组件时,在每次需要组件提供的功能时,它们通常会实例化一个新对象。比如,在客户处理文件时就是如此:各个文件用不同的对象表示,在任何时刻都可能有几个文件对象正被使用。

    不过,也有一种被叫做服务的对象,它总是只有一份拷贝(虽然在任意时刻可能有许多服务在运行)。客户每次访问服务提供的功能,都是和同一个服务实例对象打交道。比如,当一个用户在公司数据库中查找一个电话号码时,公司数据库可能就被表示为一个对象,该对象对所有的同事都是相同的。如果不同,应用程序就需要在内存中保存一个庞大数据库的两份拷贝,并且这两个拷贝之间的记录数据还可能不一致。

    提供功能的单点访问正是单态设计模式的内容,也是服务在应用程序(和开发环境,如XPCOM)中所执行的事物。

    在XPCOM最后那个,除了组件支持和管理,还有其它一些有助于开发者编写跨平台的组件的服务。这些服务包括跨平台的文件抽象(提供了统一且强大的文件访问)、目录服务(维护应用程序的位置和系统相关的位置)、内存管理(确保每个人都使用相同的内存分配器)和一个事件通知系统(允许传递简单的消息)。本教程将讲解用到的所有的这些组件和服务,在XPCOM API Reference有完整接口列表。

XPCOM类型

    在下面的示例中我们将用到XPCOM中的很多类型和简单的宏。这些类型大多数是简单的映射。最常用的类型在下面的几节介绍。

方法类型

    下面的类型用来确保正确的调用约定,并返回XPCOM方法类型。

NS_IMETHOD方法声明返回类型。XPCOM方法声明应当使用本值作为返回类型
NS_IMETHODIMP方法实现返回类型。XPCOM方法实现应当使用本值作为返回类型
NS_IMETHODIMP_(type)特殊情况下的实现返回类型。一些方法如AddRef和Release并不返回默认的返回类型。这种例外很让人遗憾,但却是COM一致性必需的
NS_IMPORT强制共享库在内部解析方法
NS_EXPORT强制共享库导出方法

引用计数

    这些宏管理引用计数.

NS_ADDREF在nsISupports对象上调用AddRef
NS_IF_ADDREF和上面相同,但在调用前会首先检查是否为null
NS_RELEASE在nsISupports对象上调用Release
NS_IF_RELEASE和上面相同,但在调用前会首先检查是否为null

状态码

    这些宏测试状态码。

NS_FAILED如果传入的状态码为失败,返回true
NS_SUCCEEDED如果传入的状态码为成功,返回true

变量映射

nsrefcnt默认引用数类型,映射为32位整数
nsresult默认错误类型,映射为32位整数
nsnull默认null值

常用XPCOM错误码

NS_ERROR_NOT_INITIALIZED实例未初始化
NS_ERROR_ALREADY_INITIALIZED实例已初始化
NS_ERROR_NOT_IMPLEMENTED方法未实现
NS_ERROR_NO_INTERFACE不支持指定的接口
NS_ERROR_NULL_POINTER指针为nsnull
NS_ERROR_FAILURE失败
NS_ERROR_UNEXPECTED未知错误
NS_ERROR_OUT_OF_MEMORY内存分配错误
NS_ERROR_FACTORY_NOT_REGISTERED请求的类未注册

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值