TOC PREV NEXT INDEX


CHAPTER 5 Tutorial: Using XPCOM Utilities To Make Things Easier


Topics covered in this chapt er:

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 the 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 Getting Started 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 the "weblock2.cpp" section at the end of the chapter with "webLock1.cpp" in the previous.

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 used in slightly different situations. Some differ only in whether or not a method is called when the module is created and/or destroyed. Table 1 lists the macros discussed in this section.

TABLE 1. XPCOM Module Macros
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 to 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 parts1 of the structure contain the following information:

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 generated with 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_ macro2.

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. But a change has gone into XPCOM recently that makes NS_INIT_ISUPPORTS no longer necessary: The mRefCnt type has been changed from an integer to a class that provides its own auto-initialization. If you are building with versions earilier than Mozilla 1.3, this macro is still required.

Table 2 summarizes the macro usage in this portion of the weblock.cpp source file:

TABLE 2. Common XPCOM Macros
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 Initalizes mRefCnt to zero. Must be called in classes 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 the next section, "weblock2.cpp".

weblock2.cpp

The listing below shows the generic module code from the "webLock1.cpp" section of the previous chapter using the macros described in this chapter.

#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) 
 

Figure 1. weblock2.cpp

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 chucks of data. The string classes can support sequences of characters broken up into multiple fragments (fragments which may or may not be null terminated)3.

All string classes in XPCOM derive from one of two abstract classes4: 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 online string guide at http://www.mozilla.org/projects/xpcom/string-guide.html). 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 = data.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.

String Size

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 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 null). 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 returns.

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 interface:

SomeClass::DoSomething(nsIFile* aFile) 
{ 
   if (! aResult) 
       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 nsCOMPtrs as much as possible in WebLock. For a complete listing of smart pointer functionality, see http://www.mozilla.org/projects/xpcom/nsCOMPtr/.

1 This section discusses the main parameters of this structure. For a complete listing of all available options you can look at the complete reference in Appendix B.

2 Note that NS_DECL_ISUPPORTS doesn't obey the general rule in which every interface has a declaration macro of the form NS_DECL_INTERFACENAME, where INTERFACENAME is the name of the interface being compiled.

3 The string classes may also support embedded nulls.

4 There 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.
TOC PREV NEXT INDEX