本章内容包括
• “Generic XPCOM Module Macros”
• “String Classes in XPCOM”
• “Smart Pointers”
• “weblock2.cpp”
XPCOM Macros
Generic XPCOM Module Macros
入门指南对于建立通用的组件代码很有用的。但是只有少数地方的代码对于WebLock组件是独一无二的,而且需要大量的输入。要写一个不同的组件库,你可以复制章节结尾的清单,做很小的改动,然后粘贴到一个新的项目。为了避免这种冗余的工作,调节编写通用代码,减少输入,XPCOM提供generic module macros扩展模块代码。由于这些宏扩展到“通用”的实现,可能不会像你在编写自己的实现那么有灵活性。但是它使开发变得更快。
宏模块包括一组宏,这些宏定义导出的NSGetModule入口点,为你的实现类创建一个通用的工厂。这些宏可以很好的管理组件的实现代码,
String Classes in XPCOM
nsEmbedString and nsEmbedCString
{
{
SomeClass::Get(nsISupports** aResult)
让你更多的去关注组件的实际逻辑。
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[] =
static const nsModuleComponentInfo components[] =
{
{ "Pretty Class Name",
CID,
CONTRACT_ID,
Constructor
},
....
}
在上面的虚构的清单中需要注意的事情是它可以支持多个组件在一个模块。Gecko的网络库在单一的nsModuleComponentInfo声明了超过50个组件。
nsModuleComponentInfo的第一个元素是组件名,虽然目前内部使用它不是那么多,这个名字应该是描述模块的。
#include "nsIGenericFactory.h"
Common Implementation Macros
NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)
nsISimpleEnumerator只包含了构造函数和析构函数并没有其他函数,而是使用了 NS_DECL_宏。
NS_IMPL_ISUPPORTSn 为带有n个接口的类实现nsISupports
第二项是 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结构中,
static const nsModuleComponentInfo components[] =
{
{ "Pretty Class Name",
SAMPLE_CID,
"@company.com/sample"
SampleConstructor
}
}
NS_IMPL_NSGETMODULE(nsSampleModule, components)
每个XPCOM对象都实现nsISupports,但是一遍又一遍地写这个实现是乏味的。可以使用XPCOM提供实现宏,除非你有非常特别的要求要管理引用计数或处理接口的发现。不用自己实现nsISupports,NS_IMPL_ISUPPORTS1可以为每个对象扩展AddRef,Release和QueryInterface的实现,
NS_IMPL_ISUPPORTS1(classname, interface1)
如果你实现多个接口,你可以简单的改变宏的“1”为你支持接口的数量,
NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
NS_IMPL_ISUPPORTSn(classname, interface1, …, interfacen)
这些宏自动添加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() {}
};
使用这些声明宏不仅节省了大量写代码的时间,如果你改变IDL文件,也还可以节省时间。因为c++头文件将自动包含更新的方法列表。
NS_DECL_ISUPPORTS 声明nsISupports的方法包括mRefCnt
NS_INIT_ISUPPORTS 初始化mRefCnt为零。必须在类构造函数中调用
NS_GET_IID 返回一个给定名称的接口的IID。接口必须由XPIDL生成。
weblock2.cpp
使用了宏的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,你必须给它返回一个引用。如下所示:
{
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,