宏模块包括一组
宏,这些宏定义导出的NSGetModule入口点,
为你的实现类
创建一个通用的工厂。这些宏可以很好的管理
组件的实现代码,
NS_IMPL_NSGETMODULE
(name, components) 用模块名称_name 和组件名称
_components.实现了nsIModule 接口
NS_IMPL_NSGETMODULE_WITH_CTOR
(name, components, ctor) 同上,但是可以指定当创建模块时调用
一个特殊函数
NS_IMPL_NSGETMODULE_WITH_DTOR
(name, components, dtor) 同上,但是可以指定当析构模块时调用一个特殊函数
NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR
(name, components, ctor, dtor) 同上,但是可以分别指定当创建模块和析构模块时调 用特殊函数
Module Implementation Macros
一般情况下用NS_IMPL_NSGETMODULE ,
不需要任何回调,但所有的宏遵循相同的一般模式,
所有这些宏的数组结构由_components参数表示,
每个结构描述的是
在XPCOM注册
CID 。
这些宏的第一个参数是一个任意的字符串的模块名称,
在调试环境中,
当组件库加载或卸载时,
这个字符串将打印到屏幕上。
你应该选择一个对跟踪信息有帮助的名称,
结构
所需的四个部分
包含以下信息:
• 可读的类名
• CID
• contract ID
•
一个给定对象的构造函数
static const nsModuleComponentInfo components[] =
{
{ "Pretty Class Name",
CID,
CONTRACT_ID,
Constructor
},
....
}
在上面的虚构的清单中
需要注意
的事情是
它可以支持多个组件在一个模块。Gecko的
网络库在单一的nsModuleComponentInfo声明了超过50个组件。
nsModuleComponentInfo的第一个元素是组件名,
虽然目前内部使用它不是那么多,
这个名字应该是描述模块的。
第二项是 CID,通常的做法是把CID定义为宏,然后在组件列表中用CID的宏,许多CID的格式如下:
#define NS_IOSERVICE_CID \
{ /* 9ac9e770-18bc-11d3-9337-00104ba0fd40 */ \
0x9ac9e770, \
0x18bc, \
0x11d3, \
{0x93, 0x37, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
}
下一个是Contract ID字符串, 这通常也在头文件用 #define进行定义。
这三个条目是
RegisterFactoryLocation方法
所需的参数,
当你使用这些实现宏,
你必须声明一个
对象的
构造函数。
这使你不必编写一个工厂对象。
Factory Macros.
工厂宏使得很容易写工厂实现,给定一个类名ConcreteClass,
工厂的宏的声明是:
NS_GENERIC_FACTORY_CONSTRUCTOR(ConcreteClass)
由此产生一个名为ConcreteClassConstructor的函数,该函数可以用在nsModuleComponentInfo结构中,
#include "nsIGenericFactory.h"
static const nsModuleComponentInfo components[] =
{
{ "Pretty Class Name",
SAMPLE_CID,
"@company.com/sample"
SampleConstructor
}
}
NS_IMPL_NSGETMODULE(nsSampleModule, components)
Common Implementation Macros
每个XPCOM对象都实现nsISupports,但是一遍又一遍地写这个实现是乏味的。可以使用XPCOM提供实现宏,
除非你有非常特别的要求要管理引用计数或处理接口的发现。
不用
自己
实现nsISupports,
NS_IMPL_ISUPPORTS1可以为每个对象扩展AddRef,Release和QueryInterface的实现,
NS_IMPL_ISUPPORTS1(classname, interface1)
如果你实现多个接口,
你可以简单的改变宏的“1”为
你支持接口的数量,
NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
NS_IMPL_ISUPPORTSn(classname, interface1, …, interfacen)
这些宏自动添加nsISupports条目,
所以你不需要这样做:
NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)
仔细看看上面的例子,
注意,它使用的是接口实际名称而不是一个IID,
在宏观内部,借,接口名称扩展为NS_GET_IID() ,这个宏
从接口生成的头的文件
提取IID,
当用XPIDL写一个接口,
头文件包含他们的静态声明IID。任何由XPIDL生成的接口,你都可以用 NS_GET_IID()获取接口的IID,
// returns a reference to a shared nsIID object.
static const nsIID iid1 = NS_GET_IID(nsISupports);
// constructs a new nsIID object
static const nsIID iid2 = NS_ISUPPORTS_IID;
为了使用NS_IMPL_ISUPPORTSn,
你必须确保在你的类定义一个nsrefcnt 类型,名字为mRefCnt的成员变量。
当你可以使用另一个宏,
何必多此一举。
Declaration Macros
NS_DECL_NSISUPPORTS 为你声明了AddRef , Release , 和QueryInterface,也定义了NS_IMPL_ISUPPORTS 需要的 mRefCnt 。
此外,
NS_DECL_附加任何接口
全部大写
名称,将
为你
声明接口的
所有方法。例如, NS_DECL_NSIFOO
声明
nsIFoo的的所有方法,nsIFoo存在于由XPIDL编译器生成nsIFoo.h文件中。
考虑下面的真正的类:
class myEnumerator : public nsISimpleEnumerator
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISIMPLEENUMERATOR
myEnumerator();
virtual ~myEnumerator() {}
};
nsISimpleEnumerator只包含了构造函数
和析构函数并没有其他函数,而是使用了 NS_DECL_宏。
使用这些声明宏不仅节省了大量写代码的时间,
如果你改变IDL文件,也
还可以节省时间。
因为c++头文件将自动包含更新的
方法
列表。
NS_IMPL_ISUPPORTSn 为带有n个接口的类实现nsISupports
NS_DECL_ISUPPORTS
声明nsISupports的方法包括mRefCnt
NS_INIT_ISUPPORTS
初始化mRefCnt为零。必须在类构造函数中调用
NS_GET_IID
返回一个给定名称的接口的IID。接口必须由XPIDL生成。
使用了宏的weblock1.cpp代码如下:
#include "nsIGenericFactory.h"
#define SAMPLE_CID \
{ 0x777f7150, 0x4a2b, 0x4301, \
{ 0xad, 0x10, 0x5e, 0xab, 0x25, 0xb3, 0x22, 0xaa}}
class Sample: public nsISupports {
public:
Sample();
virtual ~Sample();
NS_DECL_ISUPPORTS
};
Sample::Sample()
{
// note: in newer versions of Gecko (1.3 or later)
// you don’t have to do this:
NS_INIT_ISUPPORTS();
}
Sample::~Sample()
{
}
NS_IMPL_ISUPPORTS(Sample, nsISupports);
NS_GENERIC_FACTORY_CONSTRUCTOR(Sample);
static const nsModuleComponentInfo components[] =
{
{ "Pretty Class Name",
SAMPLE_CID,
"@company.com/sample"
SampleConstructor
}
};
NS_IMPL_NSGETMODULE(nsSampleModule, components)
String Classes in XPCOM
字符串通常看成是
线性序列的字符,
在c++中,字符串“XPCOM”,
由连续的6个字节组成,其他类型的字符串比如
“宽”字符串,使用两个字节来表示每个字符,
通常用于处理Unicode字符串。
然而,XPCOM的字符串类不仅仅是限于以空字符结尾的字符序列。它们是相当复杂的,因为它们支持Gecko布局引擎和其他管理大型块数据子系统。该字符串类可以支持把字符序列分解为多个片段。
XPCOM的所有字符串类派生自两个抽象类之一, nsAString
和nsACString 。
前一个处理双字节字符,
后者往往在更一般的情况下使用。
但这两个类都定义了字符串的功能。在以下章节,
你可以看到这些类作为参数传递的许多XPCOM的接口。
Using Strings
解释所有的字符串类工作超出了本书的范围,但我们
向你展示WebLock组件如何使用字符串。
首先要注意的是,字符串类本身是不冻结的,这意味着
当你可以避免的时候
不应该与它们连接。
连接字符串库到一个组件会增加
超过100 k内存消耗,这
在很多情况下是不可接受的。对于WebLock,
字符串类只需要包装现有的字符串数据,
获得付出很小的代价获得高级功能。WebLock的字符串类不需要附加,连接,搜索,
任何其他字符串数据上的实际工作,它们只需要表示 char*和
其他数据,然后传给
需要接收
nsACString参数的
方法。
nsEmbedString and nsEmbedCString
在本教程中使用的字符串是 nsEmbedString 和nsEmbedCString,他们分别实现抽象类 nsAString 和 nsACString。
第一个例子展示了一个nsEmbedCString被用来传递给需要nsACString参数的方法。
// in IDL: method(in ACString thing);
char* str = "How now brown cow?";
nsEmbedCString data(str);
rv = object->Method(data);
// in IDL: attribute ACString data;
nsEmbedCString data;
method->GetData(data);
// now to extract the data from the url class:
const char* aStringURL = data.get();
注意,调用url.get()后aStringURL 指向的内存 是URL字符串对象所有的。
如果你需要在
字符串对象的生命周期过了之后继续使用
这个字符串数据,你必须进行复制。
Smart Pointers
到目前为止你看到的所有的接口都是引用计数的。
漏一个引用而不释放的对象,
如以下代码所示,可以是一个
重大的问题。
{
nsISupports* value = nsnull;
object->method(&value);
if (!value) return;
...
if (NS_FAILED(error))
return; // <------------ leaks |value|
...
NS_RELEASE(value); // release our reference
}
方法返回一个nsISupports接口指针,在返回之前
已经被引用。
如果你
通过过早返回
处理一个错误条件,
value
指向
没有被删除而
有内存泄露。
在这个例子很容易修改,
但在实际代码,
这在“goto”结构中容易出现或者
在深嵌套中过早返回。
有多个需要被释放的接口指针,
当跳出一个块的作用域。
这就需要一个
可以帮助开发人员的
工具。
在XPCOM,
这个工具是nsCOMPtr,
或智能指针类,当你处理接口指针是
这可以为你节省大量时间和简化你的代码。
使用智能指针, 上面的代码可以简化为:
{
nsCOMPtr<nsISupports> value;
object->method(getter_AddRefs(value));
if (!value) return;
...
if (NS_FAILED(error))
return;
...
}
风格和语法
可能不熟悉,
但智能指针是值得学习和使用的,因为它们简化了
引用
管理。
nsCOMPtr是c++ 模板类,几乎就像原始指针,
可以比较和测试,等等。
当你把它们传给getter,你必须做点特别的事情,不管怎么样,
你必须
用函数
getter_AddRefs
包装
变量,如上面的示例。
你不能为nsCOMPtr调用nsISupports 的AddRef 和Release方法。
但是这个限制是可取的,因为
nsCOMPtr已经
替你
处理了引用计数。
如果由于某种原因需要调整引用计数,
您必须把nsCOMPtr赋值给一个新的变量然后调用AddRef 。
这是一个常见的模式,你在
函数中
有一个本地nsCOMPtr,你必须给它返回一个引用。
如下所示:
SomeClass::Get(nsISupports** aResult)
{
if (! aResult)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsISupports> value;
object->method(getter_AddRefs(value));
*aResult = value.get();
NS_IF_ADDREF(*aResult);
return NS_OK ;
}
该方法所做的第一件事是检查调用者是否传入一个有效的地址,如果不是,
它不再继续。接着,
它调用另一个在这种情况下是假定存在的对象的方法,
你可以调用nsCOMPtr的. get()方法,并
把它用作
原始指针
返回。
这种原始指针可以赋值给一个变量,然后用NS_IF_ADDREF更新引用。不管怎么样,小心
. get()的返回结果。
你不应该在这个结果调用Release,因为它可能会导致崩溃。
nsCOMPtr
显式地释放对象,你可以赋值指针为0。
智能指针的另一个不错的功能,
你可以很容易QueryInterface它们。
有两个接口代表一个文件系统上的文件,nsIFile 和 nsILocalFile ,
他们都是通过对象实现,
虽然我们还没有正式介绍这两个接口,
接下来的代码示例展示了如何简单切换这两个接口,
SomeClass::DoSomething(nsIFile* aFile)
{
if (! aResult)
return NS_ERROR_NULL_POINTER;
nsresult rv;
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(aFile, &rv);
如果QueryInterface成功, localFile
将非空, rv 将为
NS_OK。
如果QueryInterface失败, localFile
将为空,rv将
设置为一个特定的错误代码对应于失败的原因,
在这个构造中,
结果代码rv是一个可选参数,
如果你不关心错误代码,您可以简单地把它从函数调用去掉。
从现在开始,
我们将
在WebLock尽可能多的
使用nsCOMPtr,