This chapter goes back over the code you've already created in the first part of the tutorial (see webLock1.cpp in the previous chapter) and uses XPCOM tools that make coding a lot easier and more efficient. It also introduces a basic string type that is used with many of the APIs in both XPCOM and Gecko.
To begin with, the first section describes C++ macros that can replace a lot of the code in webLock1.cpp
. Much of the code created to get the software recognized and registered as a component can be reduced to a small data structure and a single macro.
XPCOM Macros
The XPCOM framework includes a number of macros for making C++ development easier. Though they overlap somewhat (e.g., high-level macros expand to other macros), they fall into the following general categories.
Generic XPCOM Module Macros
The work in the previous chapter was useful in setting up the generic component code. But there are only a few places in that code that are unique to the WebLock component, and it was a lot of typing. To write a different component library, you could copy the listing at the end of the chapter, change very little, and paste it into a new project. To avoid these kinds of redundancies, to regulate the way generic code is written, and to save typing, XPCOM provides generic module macros that expand into the module code you've already seen.
Since these macros expand into "generic" implementations, they may not offer as much flexibility as you have when you are writing your own implementation. But they have the advantage of allowing much more rapid development. To get an idea about how much can be handled with the macros described in this section, compare the code listing in weblock2.cpp at the end of the chapter with webLock1.cpp in the previous chapter.
The module macros include one set of macros that define the exported NSGetModule
entry point, the required nsIModule
implementation code and another that creates a generic factory for your implementation class. Used together, these macros can take care of a lot of the component implementation code and leave you working on the actual logic for your component.
Note that all of the macros described in this section are similar but are used in slightly different situations. Some differ only in whether or not a method is called when the module is created and/or destroyed. XPCOM Module Macros lists the macros discussed in this section.
Macro | Description |
NS_IMPL_NSGETMODULE(name, components) |
Implements the nsIModule interface with the module name of name and the component list in components . |
NS_IMPL_NSGETMODULE_WITH_CTOR(name, components, ctor) |
Same as above but allows for a special function to be called when the module is created. |
NS_IMPL_NSGETMODULE_WITH_DTOR(name, components, dtor) |
Same as the first macro but allows for a special function to be called when the module is destroyed. |
NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(name, components, ctor, dtor) |
This combines the last two macros so that you can define functions to be called at the construction and destruction of the module object. |
Module Implementation Macros
The general case is to use NS_IMPL_NSGETMODULE
, which doesn't take any callbacks, but all the macros follow the same general pattern. All of these macros work on an array of structures represented by the components
parameter. Each structure describes a CID that is to be registered with XPCOM.
The first parameter for each of these macros is an arbitrary string that names the module. In a debugging environment, this string will be printed to the screen when the component library is loaded or unloaded. You should pick a name that makes sense and helps you keep track of things. The four required parts[other-parts] of the structure contain the following information:
- A human readable class name
- the class ID (CID)
- the contract ID (an optional but recommended argument)
- a constructor for the given object
static const nsModuleComponentInfo components[] = { { "Pretty Class Name", CID, CONTRACT_ID, Constructor }, // ... };
The important thing to note in the fictitious listing above is that it can support multiple components in a module. Modules such as the networking libraries in Gecko ("necko") have over 50 components declared in a single nsModuleComponentInfo
array like this.
The first entry of the nsModuleComponentInfo
above is the name of the component. Though it isn't used that much internally at the present time, this name should be something that meaningfully describes the module.
The second entry of the nsModuleComponentInfo
is the CID. The usual practice is to put the class ID (CID) into a #define
and use the define to declare the CID in the components list. Many CIDs take the following form:
#define NS_IOSERVICE_CID \ { /* 9ac9e770-18bc-11d3-9337-00104ba0fd40 */ \ 0x9ac9e770, \ 0x18bc, \ 0x11d3, \ {0x93, 0x37, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ }
The next entry is the Contract ID string, which is also usually defined in a #define
in a header file.
These three entries constitute the required parameters for the RegisterFactoryLocation
method we looked at in the prior chapter. When you use these implementation macros, you must declare a constructor for the object, and this keeps you from having to write a factory object.
Factory Macros
The factory macro makes it easy to write factory implementations. Given the class name ConcreteClass
, the factory macro declaration is:
NS_GENERIC_FACTORY_CONSTRUCTOR(ConcreteClass)
This results in a function called ConcreteClassConstructor
that can be used in the nsModuleComponentInfo
structure.
#include "nsIGenericFactory.h" static const nsModuleComponentInfo components[] = { { "Pretty Class Name", SAMPLE_CID, "@company.com/sample", SampleConstructor } } NS_IMPL_NSGETMODULE(nsSampleModule, components)
Most of the components in the Mozilla browser client use this approach.
Common Implementation Macros
Every XPCOM object implements nsISupports
, but writing this implementation over and over is tedious. Unless you have very special requirements for managing reference counting or handling interface discovery, the implementation macros that XPCOM provides can be used. Instead of implementing the nsISupports
yourself, NS_IMPL_ISUPPORTS1
can expand to the implementation of AddRef
, Release
, and QueryInterface
for any object.
NS_IMPL_ISUPPORTS1(classname, interface1)
Also, if you implement more than one interface, you can simply change the 1
in the macro to the number of interfaces you support and list the interfaces, separated by commas. For example:
NS_IMPL_ISUPPORTS2(classname, interface1, interface2) NS_IMPL_ISUPPORTSn(classname, interface1, ..., interfacen)
These macros automatically add the nsISupports
entry for you, so you don't need to do something like this:
NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)
Take a close look at the above example. Note that it uses the actual name of the interface and not an IID. Inside the macro, the interface name expands to NS_GET_IID()
, which is another macro that extracts the IID from the generated header of the interface. When an interface is written in XPIDL, the headers include static declarations of their IIDs. On any interface that is generated by XPIDL, you can call NS_GET_IID()
to obtain the IID which is associated with that interface.
// 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;
In order to use NS_IMPL_ISUPPORTSn
, you must be sure that a member variable of type nsrefcnt
is defined and named mRefCnt
in your class. But why even bother when you can use another macro?
Declaration Macros
NS_DECL_NSISUPPORTS
declares AddRef
, Release
, and QueryInterface
for you, and it also defines the mRefCnt
required by NS_IMPL_ISUPPORTS
. Furthermore, NS_DECL_
appended with any interface name in all caps will declare all of the methods of that interface for you. For example, NS_DECL_NSIFOO
will declare all of the methods of nsIFoo
provided that it exists and that nsIFoo.h
was generated by the XPIDL compiler. Consider the following real class:
class myEnumerator : public nsISimpleEnumerator { public: NS_DECL_ISUPPORTS NS_DECL_NSISIMPLEENUMERATOR myEnumerator(); virtual ~myEnumerator() {} };
The declaration of this nsISimpleEnumerator
class doesn't include any methods other than the contructor and destructor. Instead, the class uses the NS_DECL_
macro[nsISupports-warning].
Using these declaration macros not only saves a tremendous amount of time when you're writing the code, it can also save time if you make changes to your IDL file, since the C++ header file will then automatically include the updated list of methods to be supported.
The NS_INIT_ISUPPORTS
macro is also a bit of a special case. Historically, it gets called in the constructor for your class and sets mRefCnt
to zero. However, a change in XPCOM that occurred before Mozilla 1.3 makes NS_INIT_ISUPPORTS
no longer necessary: mRefCnt
's type has been changed from an integer to a class that provides its own auto-initialization. If you are building with versions earlier than Mozilla 1.3, this macro is still required.
The following table summarizes the macro usage in this portion of the weblock.cpp
source file:
NS_IMPL_ISUPPORTSn |
Implements nsISupports for a given class with n number of interfaces |
NS_DECL_ISUPPORTS |
Declares methods of nsISupports including mRefCnt |
NS_INIT_ISUPPORTS |
Initializes mRefCnt to zero. Must be called in class's constructor |
NS_GET_IID |
Returns the IID given the name of an interface. Interface must be generated by XPIDL |
Using the macros described here, the code for the WebLock component has gone from around 340 lines of code to just under 40. Clearly from a code maintenance point of view, this kind of reduction is outstanding. The entire source file with these macros included appears in weblock2.cpp.
weblock2.cpp
The listing below shows the generic module code from webLock1.cpp using the macros described in this chapter:
#include "nsIGenericFactory.h" #include "nsISupportsUtils.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_ISUPPORTS1(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
Strings are usually thought of as linear sequences of characters. In C++, the string literal "XPCOM", for example, consists of 6 consecutive bytes, where `X' is at byte offset zero and a null character is at byte offset 5. Other kinds of strings like "wide" strings use two bytes to represent each character, and are often used to deal with Unicode strings.
The string classes in XPCOM are not just limited to representing a null terminated sequence of characters, however. They are fairly complex because they support the Gecko layout engine and other subsystems that manage large chunks of data. Additionally, in some versions of Mozilla the string classes support sequences of characters broken up into multiple fragments (fragments which may or may not be null terminated)[nulls-in-strings].
All string classes in XPCOM derive from one of two abstract classes[other-string-classes]: nsAString
and nsACString
. The former handles double byte characters, and the latter tends to be used in more general circumstances, but both of these classes define the functionality of a string. You can see these classes being passed as arguments in many of the XPCOM interfaces we'll look at in the following chapters.
Using Strings
Explaining how all the string classes work is outside the scope of this book, but we can show you how to use strings in the WebLock component. The first thing to note is that the string classes themselves are not frozen, which means that you should not link against them when you can avoid it.
Linking the full string library (.lib
or .a
) into a component may raise its footprint by more than 100k (on Windows), which in many cases is an unacceptable gain (see the XPCOM string guide). For WebLock, where the string classes need to be only wrappers around already existing string data, trading advanced functionality for a much smaller footprint is the right way to go. The WebLock string classes don't need to append, concatenate, search, or do any other real work on the string data; they just need to represent char*
and other data and pass them to methods that expect an nsACString
.
nsEmbedString
and nsEmbedCString
The strings used in this tutorial are nsEmbedString
and nsEmbedCString
, which implement the nsAString
abstract class and the nsACString
abstract classes, respectively. This first example shows an nsEmbedCString
being used to pass an nsACString
to a method that's not expected to modify the string.
// in IDL: method(in ACString thing); char* str = "How now brown cow?"; nsEmbedCString data(str); rv = object->Method(data);
In this next example, the method is going to set the value of the string - as it might need to do when it returns the name of the current user or the last viewed URL.
// in IDL: attribute ACString data; nsEmbedCString data; method->GetData(data); // now to extract the data from the url class: const char* aStringURL = url.get();
Note that the memory pointed to by aStringURL
after the call to url.get()
is owned by the URL string object. If you need to keep this string data around past the lifetime of the string object, you must make a copy.
The examples above illustrate the use of the single byte string class, nsEmbedCString
. The double byte version, nsEmbedString
, has the same functionality but the constructor takes nsAString
and the .get() method returns the type PRUnichar*
. Note that PRUnichar
is a two byte value. In the coming chapters, you'll see examples that use this version in the WebLock component.
Smart Pointers
All of the interfaces that you've seen so far are reference counted. Leaking a reference by not releasing an object, as the code below demonstrates, can be a major problem.
{ nsISupports* value = nsnull; object->method(&value); if (!value) return; // ... if (NS_FAILED(error)) return; // <------------ leaks |value| //... NS_RELEASE(value); // release our reference }
A method returns an nsISupports
interface pointer that has been reference counted before it is returned (assuming it wasn't nsnull
). If you handle an error condition by returning prematurely, whatever value points at will leak-it will never be deleted. This is a trivial fix in this example, but in real code, this can easily happen in goto
constructs, or in deep nesting with early return
s.
Having more than one interface pointer that needs to be released when a block goes out of scope begs for a tool that can aid the developer. In XPCOM, this tool is the nsCOMPtr
, or smart pointer class, which can save you countless hours and simplify your code when you're dealing with interface pointers. Using smart pointers, the code above can be simplified to:
{ nsCOMPtr<nsISupports> value; object->method(getter_AddRefs(value)); if (!value) return; // ... if (NS_FAILED(error)) return; // ... }
The style or syntax may be unfamilar, but smart pointers are worth learning and using because they simplify the task of managing references. nsCOMPtr
is a C++ template class that acts almost exactly like raw pointers, that can be compared and tested, and so on. When you pass them to a getter, you must do something special, however: You must wrap the variable with the function getter_AddRefs
, as in the example above.
You cannot call the nsISupports
AddRef
or Release
methods on a nsCOMPtr
. But this restriction is desirable, since the nsCOMPtr
is handling reference counting for you. If for some reason you need to adjust the reference count, you must assign the nsCOMPtr
to a new variable and AddRef
that. This is a common pattern when you have a local nsCOMPtr
in a function and you must pass back a reference to it, as in the following:
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; }
The first thing that this method does is check to see that the caller passed a valid address. If not, it doesn't even try to continue. Next, it calls another method on an object that is presumed to exist in this context. You can call a .get()
method on the nsCOMPtr
and have it returned for use as a raw pointer. This raw pointer can then be assigned to a variable and have its reference updated by NS_IF_ADDREF
. Be very careful with the result of .get()
, however. You should never call Release
on this result because it may result in a crash. Instead, to explicitly release the object being held by a nsCOMPtr
, you can assign zero to that pointer.
Another nice feature of smart pointers - the part that makes them smart - is that you can QueryInterface
them quite easily. For example, there are two interfaces for representing a file on a file system, the nsIFile
and nsILocalFile
, and they are both implemented by an object. Although we haven't formally introduced these two interfaces, the next code sample shows how simple it is to switch between these two interfaces:
SomeClass::DoSomething(nsIFile* aFile) { if (!aFile) return NS_ERROR_NULL_POINTER; nsresult rv; nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(aFile, &rv); // ... }
If the QueryInterface
is successful, localFile
will be non-null, and rv
will be set to NS_OK
. If QueryInterface
fails, localFile
will be null, and rv
will be set to a specific error code corresponding to the reason for the failure. In this construct, the result code rv
is an optional parameter. If you don't care what the error code is, you can simply drop it from the function call.
From this point on, we'll be using nsCOMPtr
s as much as possible in WebLock. For a complete listing of smart pointer functionality, see mozilla.org's nsCOMPtr
documentationXXX this should be in devmo.
- Note: other-partsThis section discusses the main parameters of this structure. For a complete listing of all available options you can look at the complete reference in the XPCOM API Reference.
- Note: nsISupports-warningNote that
NS_DECL_ISUPPORTS
doesn't obey the general rule in which every interface has a declaration macro of the formNS_DECL_INTERFACENAME
, whereINTERFACENAME
is the name of the interface being compiled. - Note: nulls-in-stringsThe string classes may also support embedded nulls.
- Note: other-string-classesThere are other abstract string classes, but they are outside the scope of this book.
Copyright (c) 2003 by Doug Turner and Ian Oeschger. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.02 or later. Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.