8.2. Creating XPCOM Components

As we mentioned, one advantage of using XPCOM is that it separates the implementation from the interface so you can write a component in a language-agnostic manner. The services your component provides are available to all other components despite the language used to implement it. This means, for example, that you can use JavaScript not only to access the services of an XPCOM component, but also to create those services. As described in Chapter 5, using JavaScript as a modularized application programming language provides the deepest level of scripting in Mozilla.

In your Mozilla build or distribution, you will find a subdirectory named components. Inside this directory, you will see many compiled components. You will also see a number of JavaScript components. If you look at the source of these components, you can get an idea of how a JavaScript component is created. For example, look at the files nsFilePicker.js and nsSidebar.js. These JavaScript components are used in the Mozilla distribution.

JavaScript XPCOM components have the advantage over regular scripts of being fast, reusable, and globally accessible to any caller. They also have the advantage over C++-based XPCOM components of being easier to write and maintain. The next few sections describe the creation of a JavaScript-based XPCOM component. If you would rather do your work in C++, then skip to the C++ implementation section in this chapter.

8.2.1. Creating a JavaScript XPCOM Component

To create a JavaScript component, you need to create an IDL interface source file and a JavaScript implementation source file. In the Mozilla sources, naming source files with an ns prefix is common practice, so the implementation file should be called something like nsSimple.js. The interface source file, or IDL file, uses a similar convention: it is typical for interfaces to begin with nsI, using an I to distinguish them as interfaces rather than implementations. Call the IDL source file nsISimple.idl.

In addition to these two source files (nsSimple.js and nsISimple.idl), you will compile a cross platform binary interface file, or type library, with the XPIDL compiler, calling it nsISimple.xpt. This .xpt file tells Mozilla that the interface is available and scriptable. You can use it on any platform that Mozilla supports. In other words, you can pick up nsISimple.xpt, which may have been compiled on Unix, drop it into Windows or Mac OS, and use it.

All .xpt interface files for Mozilla live in the components directory located in mozilla/dist/bin if you are developing with the Mozilla source code. Otherwise, for binary distributions of Mozilla, they are located in mozilla/components. Mozilla checks this directory upon start up, looking for any new components to register automatically.

8.2.1.1. The XPIDL interface source file

Usually, the first step in creating a new component is writing the interface. To begin, open up your favorite text editor and create a new file called nsISimple.idl.

The complete source code for the nsISimple.idl interface file is:

#include "nsISupports.idl"
[scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)]
interface nsISimple : nsISupports
{
    attribute string yourName;
    void write( );
    void change(in string aValue);
};

The #include line above includes the file nsISupports.idl, which defines this interface's base class. The [scriptable, uuid..] line declares the interface scriptable and assigns a UUID to the interface. You can use the UUID provided, but creating your own using one of the UUID generation tools described earlier is usually better. The third line, next to the interface keyword, declares the interface's name, nsISimple, and says that it derives from nsISupports.

Various attributes and methods are defined within the definition of the nsISimple interface. Attributes are properties of interface objects. They may be read-only or read/write variables. In nsISimple, an attribute called yourName is of the type string. In this implementation, you may get and set this attribute's value. Of the methods defined in this interface, the write( ) method takes no arguments and the change( ) method takes an argument of type string called aValue. The parameter aValue will be a new value that replaces the current value held by yourName. The complete interface IDL is:

#include "nsISupports.idl"
[scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)]
interface nsISimple : nsISupports
{
    attribute string yourName;
    void write( );
    void change(in string aValue);
};

8.2.1.2. JavaScript implementation file

Once you have created an interface file that publicly defines the component's methods and attributes, the next step is to implement those methods and attributes in a separate source file. The listings below walk through the implementation of nsISimple step by step.

First, you must declare an empty function called SimpleComponent, which is a standard constructor for a JavaScript object prototype. It's a good idea to name the component in a way that clearly describes both the component and the interface, as SimpleComponent does (i.e., SimpleComponent is an implementation of the nsISimple interface):

function SimpleComponent( ) {}

With the function declared, we start defining the JavaScript class prototype.

SimpleComponent.prototype = {
    mName : "a default value",

In the prototype, we first create a member variable called mName that will be the string placeholder for the IDL attribute yourName. The variable assigns the string a default value. Remember to place commas after all definitions in a prototype. IDL attributes are always implemented as getter functions. Methods marked with [noscript] will not be available for use with scripting languages.

Next we implement the functions below for our definition of attribute string yourName in our file nsISimple.idl.

    get yourName( )      { return this.mName; },
    set yourName(aName) { return this.mName = aName; },

When someone calls an IDL attribute in an interface, getters and setters are used to get or set values for the attribute:

simple.yourName='foo';

Or similarly read values from the attribute:

var foo = simple.yourName;

We first call on the setter function to set a value to the attribute yourName and then use the getter function to obtain the currently set value of yourName.

The first function defined in nsISimple is called void write( ). For this method, the implementation can be as simple as the following code:

    write : function ( ) { dump("Hello " + this.mName + "\n"); },

This example implements the declaration void write( ) by dumping the current value of the variable mName to stdout. The code uses the this keyword to indicate that you are calling to the component's own member variable mName.

The void change( ) method is then implemented as follows:

    change : function (aValue) { this.mName = aValue; },

change( ) is a method used to change the value variable.

8.2.1.3. Implementing the required XPCOM methods in JavaScript

Once the definitions in the nsISimple interface are implemented, you need to implement required methods and factories that make this JavaScript implementation class an XPCOM component. Recall that all XPCOM components must implement the nsISupports interface.

Example 8-3 shows an implementation of QueryInterface specific to our new component. QueryInterface ensures that the correct interface (nsISimple) is used by matching the iid with the nsISimple interface that this component implements. If the interface doesn't match, then the argument is invalid. In this case, the exception Components.results.NS_ERROR_NO_INTERFACE is thrown, which maps to the error code number 2147500034, and code execution is stopped. If the interface identifier parameter matches the interface, then an instance of the implementation class object SimpleComponent with its interface is returned as a ready-to-use XPCOM component. In XPCOM, every component you implement must have a QueryInterface method.

The next requirement is to create a JavaScript object called Module. This module implements the methods needed for autoregistration and component return type objects.

var Module = {
    firstTime  : true,

The Boolean firstTime is a flag used only when the component is initially registered:

registerSelf: function (compMgr, fileSpec, location, type) {
  if (this.firstTime) {
    dump("*** first time registration of Simple JS component\n");
    this.firstTime = false;
    throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  }

The Component Manager can do a lot in the registration process, but you have to add some logic for first time registration so the Component Manager has the information it needs. RegisterSelf is called at registration time (component installation) and is responsible for notifying the component manager of all components implemented in this module. The fileSpec, location, and type parameters can be passed on to the registerComponent method unmolested. Next, register the component with the Component Manager using code like the following example. The parameters include the CID, a description, a progID, and the other parameters you can pass without changing:

  dump(" ***** Registering: Simple JS component! ****\n");
  compMgr.registerComponentWithType(this.myCID,
            "My JS Component",
            this.myProgID, fileSpec,
            location, true, true,
            type);
    },

The GetClassObject method produces Factory and SingletonFactory objects. Singleton objects are specialized for services that allow only one instance of the object. Upon success, the method returns an instance of the components factory, which is the implementation class less its interface:

getClassObject : function (compMgr, cid, iid) {
  if (!cid.equals(this.myCID))
      throw Components.results.NS_ERROR_NO_INTERFACE;
  if (!iid.equals(Components.interfaces.nsIFactory))
      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  return this.myFactory;
  },

In the previous list, the member variables myCID and myProgID are the class ID and the human-readable canonical program ID, respectively:

    myCID: Components.ID("{98aa9afd-8b08-415b-91ed-01916a130d16}"),
    myProgID: "@mozilla.org/js_simple_component;1",

The member object myFactory is the components factory, which through its own member function, createInstance( ), constructs and returns an instance of the complete component (if the iid parameter is specified and is the correct interface). Otherwise, if no iid parameter is used, the iid of nsISupports is used and an instance of the module is created that will then need a subsequent call to QueryInterface to instantiate the object as a component.

    myFactory: {
      createInstance: function (outer, iid) {
        dump("CI: " + iid + "\n");
        if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;
        return (new SimpleComponent( )).QueryInterface(iid);
  }
},

The method canUnload unloads the module when shutdown occurs and is the last function in the module. The componentManager calls the method NSGetModule to initialize these required XPCOM methods and objects:

canUnload: function(compMgr) {
  dump("****** Unloading: Simple JS component! ****** \n");
  return true;
}
function NSGetModule(compMgr, fileSpec) { return Module; }

The code in Example 8-4 shows the implementation for the nsISimple interface in its entirety.

Example 8-4. JavaScript implementation of nsISimple


function SimpleComponent(){}

SimpleComponent.prototype = {

    get yourName()        { return this.mName; },
    set yourName(aName)   { return this.mName = aName; },

    write: function () { dump("Hello " + this.mName + "\n"); },
    change: function (aValue) { this.mName = aValue; },
    mName: "a default value",

    QueryInterface: function (iid) {
        if (!iid.equals(Components.interfaces.nsISimple)
            && !iid.equals(Components.interfaces.nsISupports))
        {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    }
}

var Module = {
    firstTime: true,

    registerSelf: function (compMgr, fileSpec, location, type) {
        if (this.firstTime) {
            dump("*** Deferring registration of simple JS components\n");
            this.firstTime = false;
            throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
        }
        debug("*** Registering sample JS components\n");
        compMgr =
compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
        compMgr.registerFactoryLocation(this.myCID,
                                        "Simple JS Component",
                                        this.myProgID,
                                        fileSpec,
                                        location,
                                        type);
    },

    getClassObject : function (compMgr, cid, iid) {
        if (!cid.equals(this.myCID))
        throw Components.results.NS_ERROR_NO_INTERFACE
        if (!iid.equals(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        return this.myFactory;
    },

    myCID: Components.ID("{98aa9afd-8b08-415b-91ed-01916a130d16}"),
    myProgID: "@mozilla.org/js_simple_component;1",

    myFactory: {
        createInstance: function (outer, iid) {
            dump("CI: " + iid + "\n");
            if (outer != null)
            throw Components.results.NS_ERROR_NO_AGGREGATION;
            return (new SimpleComponent()).QueryInterface(iid);
        }
    },

    canUnload: function(compMgr) {
        dump("****** Unloading: Simple JS component! ****** \n");
        return true;
    }
}; // END Module

function NSGetModule(compMgr, fileSpec) { return Module; }

8.2.2. Compiling the Component

Once you create an IDL source file and a JavaScript implementation file, you need to compile nsISimple.idl into a .xpt type library.

8.2.3. Testing the Component

When you start up xpcshell, the Component Manager finds the new nsISimple component and registers it. The result of your test should look similar to Example 8-5.

Once the component is tested and registered as an XPCOM object, you can use JavaScript from a local web page or from the chrome to create an nsISimple object and use it as you would any ordinary JavaScript object:

<script type="application/x-JavaScript">
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var Simple=new Components.Constructor("@mozilla.org/js_simple_component;1", "nsISimple");
var s = new Simple( );
for(var list in s)
document.write(list+"<br>\n");
</script>

In addition to creating a component in JavaScript, you can implement XPCOM components in C++ and Python. The next sections cover the C++ implementation of the nsISimple interface.

8.2.4. Useful C++ Macros and Types

Before you begin working on an actual implementation of a C++ component, familiarize yourself with some of the tools that make C++ programming for XPCOM a little easier. Templates, special types, and macros can ease some of the extra housekeeping that programming XPCOM requires.

More tools than we can cover in this introduction are available, but this section reviews some of the most common, including a macro that implements the nsISupports methods QueryInterface, AddRef, and Release, macros for testing nsresults, smart pointers, and special types.

8.2.4.1. The NS_IMPL_ISUPPORTS1_CI macro

Rather than having to implement QueryInterface, AddRef, and the Release methods like we did in our JavaScript component, the NS_IMPL_ISUPPORTS macro inserts the implementation code for you.

To use this macro for the nsISimple interface, type:

NS_IMPL_ISUPPORTS1_CI(nsSimpleImpl, nsISimple)

The following lines define this macro:

#define NS_IMPL_ISUPPORTS1(_class, _interface) \
NS_IMPL_ADDREF(_class)                         \
NS_IMPL_RELEASE(_class)                        \
NS_IMPL_QUERY_INTERFACE1(_class, _interface)

As you can see, the macro is made up of other macros that implement basic methods of the nsISupports interface. Unless you need to modify these macros, they should be left as is. This macro is used later on when we create our C++ component.

Example 8-6 shows a reference implementation of the QueryInterface method in C++.

8.2.4.2. The results macros

Since all XPCOM methods return result codes called nsresults, another useful macro is the NS_SUCCEEDED macro. This indicates whether an XPCOM accessor has returned a successful result. It is defined in nsError.h:

  #define NS_SUCCEEDED(_nsresult) (!((_nsresult) & 0x80000000))

A related macro, NS_FAILED, is indicates whether an XPCOM accessor returned a failure code result. It too is defined in nsError.h. The following code demonstrates the typical use of these two macros:

nsresult rv; 
nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1", &rv));
  if (NS_FAILED(rv)) {
    printf("FAILED\n");
    return rv;
  }
  if (NS_SUCCEEDED(rv)) {
    printf(" SUCCEEDED \n");
    return rv;
  }

You may have noticed that the declaration of the identifier rv as the type nsresult. nsresult is a 32-bit unsigned integer declared in nscore.h:

  typedef PRUint32 nsresult;

We assign an nsCOMPtr or smart pointer named file to a newly created instance of the nsILocalFile component. Using the NS_FAILED and NS_SUCCEEDED macros, we test for the nsresult to see if our attempt to create an instance of the component failed. If it did, rv would be assigned an integer with a specific error return code. Return codes are defined in nsError.h. Alternatively, you can test your results for the success code:

nsresult rv = nsComponentManager::CreateInstance("@mozilla.org/file/local;1", 
                                     nsnull,
                                     NS_GET_IID(nsILocalFile), 
                                     (void **)&refp);

If a result is successful, the value of rv returns NS_OK, which is 0.

Return codes are used in XPCOM instead of exceptions. Exceptions are not allowed because of their inconsistent implementation across different compilers. All error code numbers equate to a specific type of error. For example NS_ERROR_FAILURE and NS_ERROR_NULL_POINTER are common types of error code return values used throughout the Mozilla code base. If a value returned to rv was NS_ERROR_NULL_POINTER, the test for failure would be true and the code would return the numerical result code for NS_ERROR_NULL_POINTER.

8.2.4.5. nsCOMPtr smart pointer

As described earlier, XPCOM provides a C++ tool called a smart pointer to manage reference counting. A smart pointer is a template class that acts syntactically, just like an ordinary pointer in C or C++. You can apply * to dereference the pointer, ->, or access what the pointer refers to. Unlike a raw COM interface pointer, however, nsCOMPtr manages AddRef, Release, and QueryInterface for you, thereby preventing memory leaks.

Here is how to create a raw pointer:

nsILocalFile *refp(nsnull);
nsresult rv = nsComponentManager::CreateInstance("@mozilla.org/file/local;1", 
   nsnull,
  NS_GET_IID(nsILocalFile), 
  (void **)&refp);
if (refp)
  printf("%p\n", (void*)refp);

After you create a new object that refp points to, refp is considered an owning reference, and any other pointers that point to it must be "refcounted." Example 8-9 uses anotherPtr and oneMorePtr to point to refp, and manually manages AddRef and Release.

In Example 8-9, if someCondition is false, anotherPtr is released and the function then returns (NS_OK). But what about oneMorePtr? In this instance, it is never released; if you remember, an object cannot be released from memory until our refcount is at zero. The refcount is out of sync, oneMorePtr is never decremented before the return, and the object is thus left dangling in memory. With the refcount off, the object leaks. Remember that Release( ) calls the C++ delete operator to free up the allocated XPCOM object only when the count is decremented to 0. If Release thinks there are still references to the object because the refcount hasn't been properly decremented, delete is never called. The correct code is shown below:

if (!someCondition) {
  NS_RELEASE(anotherPtr); // decrement refcount
  NS_RELEASE(oneMorePtr); // decrement refcount
  return NS_OK;
}

As you can see, manual management of reference counting is prone to error. To alleviate this burden and extra code bloat, nsCOMPtr implements AddRef and Release for you and makes life much easier. Before the nsCOMPtr class is removed from the stack, it calls Release in its destructor. After all references are properly released, delete is called and the object is freed from memory. Example 8-10 shows a typical use of nsCOMPtr.

Wherever the code returns, all pointers holding references to the nsLocalFile XPCOM object are released automatically in the nsCOMPtr class destructor before the instructions are removed from the stack. By letting nsCOMPtr manage AddRef and Release for you, you remove a margin for error, code complexity, and bloat.

8.2.5. C++ Implementation of nsISimple

Now that you have seen some of the C++ tools you need for XPCOM, you can turn to an actual implementation.

Earlier in this chapter, the section Section 8.2.1 showed you how to create an interface and implement it in JavaScript. However, you may need a C++ implementation to benefit from the better performance offered by a compiled language.

Most components used in Mozilla are written in C++. This section discusses how to create a C++ implementation for the nsISimple interface. A few more steps are involved, but as you will see, they are generally similar to the processes described in the JavaScript component section, facilitated to some extent by the available tools and templates discussed previously.

8.2.5.2. nsISimple C++ header file

Earlier, you created the type library nsISimple.xpt for the JavaScript component and installed it in the components subdirectory. Since we've already covered those steps, we can move forward to generating a C++ header file. To create a C++ header file from your original IDL, run your IDL file through the xpidl compiler:

$ xpidl -m header -w -v -I $XPIDL_INC \
> -o nsISimple nsISimple.idl

The generated file is nsISimple.h and is shown in Example 8-11.

Example 8-11. nsISimple header file generated by xpidl compiler

/*
 * DO NOT EDIT.  THIS FILE IS GENERATED FROM nsISimple.idl
 */
#ifndef _ _gen_nsISimple_h_ _
#define _ _gen_nsISimple_h_ _
#ifndef _ _gen_nsISupports_h_ _
#include "nsISupports.h"
#endif
/* For IDL files that don't want to include root IDL files. */
#ifndef NS_NO_VTABLE
#define NS_NO_VTABLE
#endif
/* starting interface:    nsISimple */
#define NS_ISIMPLE_IID_STR "ce32e3ff-36f8-425f-94be-d85b26e634ee"
#define NS_ISIMPLE_IID \
  {0xce32e3ff, 0x36f8, 0x425f, \
    { 0x94, 0xbe, 0xd8, 0x5b, 0x26, 0xe6, 0x34, 0xee }}
class NS_NO_VTABLE nsISimple : public nsISupports {
 public: 
  NS_DEFINE_STATIC_IID_ACCESSOR(NS_ISIMPLE_IID)
  /* attribute string yourName; */
  NS_IMETHOD GetYourName(char * *aYourName) = 0;
  NS_IMETHOD SetYourName(const char * aYourName) = 0;
  /* void write ( ); */
  NS_IMETHOD Write(void) = 0;
  /* void change (in string aName); */
  NS_IMETHOD Change(const char *aName) = 0;
};
/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_NSISIMPLE \
  NS_IMETHOD GetYourName(char * *aYourName); \
  NS_IMETHOD SetYourName(const char * aYourName); \
  NS_IMETHOD Write(void); \
  NS_IMETHOD Change(const char *aName); 
/* Use this macro to declare functions that forward the behavior of this interface to another object. */
#define NS_FORWARD_NSISIMPLE(_to) \
  NS_IMETHOD GetYourName(char * *aYourName) { return _to ## GetYourName(aYourName); } \
  NS_IMETHOD SetYourName(const char * aYourName) { return _to ## SetYourName(aYourName); } \
  NS_IMETHOD Write(void) { return _to ## Write( ); } \
  NS_IMETHOD Change(const char *aName) { return _to ## Change(aName); } 
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
#define NS_FORWARD_SAFE_NSISIMPLE(_to) \
  NS_IMETHOD GetYourName(char * *aYourName) { return !_to ## ? NS_ERROR_NULL_POINTER : _to ##->GetYourName(aYourName); } \
  NS_IMETHOD SetYourName(const char * aYourName) { return !_to ## ? NS_ERROR_NULL_POINTER : _to ##->SetYourName(aYourName); } \
  NS_IMETHOD Write(void) { return !_to ## ? NS_ERROR_NULL_POINTER : _to ##-> Write( ); } \
  NS_IMETHOD Change(const char *aName) { return !_to ## ? NS_ERROR_NULL_POINTER : _to ##-> Change(aName); } 
#if 0
/* Use the code below as a template for the implementation class for this interface. */
/* Header file */
class nsSimple : public nsISimple
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSISIMPLE
  nsSimple( );
  virtual ~nsSimple( );
  /* additional members */
};
/* Implementation file */
NS_IMPL_ISUPPORTS1(nsSimple, nsISimple)
nsSimple::nsSimple( )
{
  NS_INIT_ISUPPORTS( );
  /* member initializers and constructor code */
}
nsSimple::~nsSimple( )
{
  /* destructor code */
}
/* attribute string yourName; */
NS_IMETHODIMP nsSimple::GetYourName(char * *aYourName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsSimple::SetYourName(const char * aYourName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* void write ( ); */
NS_IMETHODIMP nsSimple::Write( )
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* void change (in string aName); */
NS_IMETHODIMP nsSimple::Change(const char *aName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* End of implementation class template. */
#endif
#endif /* _ _gen_nsISimple_h_ _ */

As you can see, the xpidl compiler can do a lot of work for you. The code generated in Example 8-11 is a C++ header file that declares the methods of nsISimple. It provides the class definition, macros for using the interface, and a template for the class implementation, which contains stubbed-out declaratory code that you can paste into your implementation file to quickly get started.

8.2.5.3. Creating the implementation file

The implementation file actually contains the C++ code that implements the member functions and properties declared in your interface. For nsISimple, these members are the yourName attribute and the write( ) and change( ) methods.

First you need to generate a new UUID for the new implementation class you'll write. Every XPCOM implementation class must have its own UUID:

$ uuidgen
79e9424f-2c4d-4cae-a762-31b334079252

As part of the generated file nsISimple.h, all the code stubs you need to get started are ready to be copied and pasted into the C++ source files. You can use those stubs as a guide to implement the component. In a text editor, create a new file called nsSimple.h and enter the code shown in Example 8-12.

To maintain clarity, the C++ implementation class is named nsSimpleImpl, where the default class name generated by the xpidl compiler is nsSimple and the header file, nsSimple.h, is shown in Example 8-12.

Example 8-12 includes the ID-generated header file nsISimple.h, which holds the C++ declarations for the interface class nsISimple. It then takes the new UUID and breaks it into a class ID struct defined as NS_SIMPLE_CID. Next, it defines the contract ID for this implementation class.

The example uses a completely different class ID and contract ID than the one used for the JavaScript component because it's a different implementation class and needs to have it's own unique identification (even though it implements the same interface).

Now the example makes the class declaration of the implementation, called nsSimpleImpl, which inherits from nsISimple, defining the class constructor and virtual destructor. NS_DECL_ISUPPORTS is a macro that holds the declaration of our required QueryInterface, AddRef, and Release methods. NS_DECL_NSISIMPLE is created in the generated header file nsISimple.h. It expands to the used interface method declarations. Finally Example 8-12 shows the addition of the char* member variable identified as mName. This variable is used to hold the value of the interface attribute yourName, just as it did earlier in the JavaScript class implementation.

Once you have the header file, you are ready to start the implementation source file. With a text editor, create a new file called nsSimple.cpp. As in any C++ source file, you should add the header files required by the implementation:

#include "plstr.h"
#include "stdio.h"
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "nsSimple.h"

Start by adding the implementation of our class constructor and destructor:

// c++ constructor
nsSimpleImpl::nsSimpleImpl( ) : mName(nsnull)
{
    NS_INIT_REFCNT( );
    mName = PL_strdup("default value");
}
// c++ destructor
nsSimpleImpl::~nsSimpleImpl( )
{
    if (mName)
        PL_strfree(mName);
}

Then add the macro NS_IMPL_ISUPPORTS1_CI. As discussed earlier, this macro conveniently implements QueryInterface, AddRef, and Release:

NS_IMPL_ISUPPORTS1_CI(nsSimpleImpl, nsISimple);

Next you are ready to implement the actual nsISimple interface methods:

NS_IMETHODIMP
nsSimpleImpl::GetYourName(char** aName)
{
    NS_PRECONDITION(aName != nsnull, "null ptr");
    if (!aName)
        return NS_ERROR_NULL_POINTER;
    if (mName) {
        *aName = (char*) nsMemory::Alloc(PL_strlen(mName) + 1);
        if (! *aName)
            return NS_ERROR_NULL_POINTER;
        PL_strcpy(*aName, mName);
    }
    else {
        *aName = nsnull;
    }
    return NS_OK;
}

A C++ implementation of an IDL method is declared as the type NS_IMETHODIMP. The implementation starts with the getter method GetYourName, which takes a char** parameter for the method's return value. Return values in C++ XPCOM components are marshaled via method arguments because interface implementations must always return a numerical nsresult, as described earlier. To ensure that the aName parameter is a pointer, use the macro NS_PRECONDITION to warn if null, follow with a null test in the line below, and return the error result code NS_ERROR_NULL_POINTER. Then test whether the member variable mName holds a value. If it does, allocate the necessary memory to accommodate the size of the copy. Then by using PL_strcpy, you can assign the value to the parameter aName. Otherwise, mName is null and you can assign null into aName and return:

NS_IMETHODIMP
nsSimpleImpl::SetYourName(const char* aName)
{
   NS_PRECONDITION(aName != nsnull, "null ptr");
    if (!aName)
        return NS_ERROR_NULL_POINTER;
    if (mName) {
        PL_strfree(mName);
    }
    mName = PL_strdup(aName);
    return NS_OK;
}

After implementing the getter, implement the setter. Again, use NS_PRECONDITION and then a null test on the aName. If that parameter holds data, you can free it by using PL_strfree and calling PL_strdup. Then assign the new value to class member mName:

NS_IMETHODIMP
nsSimpleImpl::Write( )
{
  printf("%s\n", mName);
  return NS_OK;
}
NS_IMETHODIMP
nsSimpleImpl::Change(const char* aName)
{
  return SetYourName(aName);
}

Finally, implement the Write and Change methods by using printf to write the value of mName to stdout and set a new value to mName. Example 8-13 shows the C++ source code in its entirety.

8.2.6. The nsSimple module code

As you needed to do with the JavaScript implementation, you must create the code for the module. The module code abstracts the implementation class and makes the implementation a component library. In your text editor, create a file called nsSimpleModule.cpp and enter the code shown in Example 8-14.

8.2.6.1. The final steps for a C++ component

Once you have an interface file nsISimple.idl, a C++ source file nsSimple.cpp with its header file nsSimple.h, and a module file nsSimpleModule.cpp, you can create a Makefile like the one shown in Example 8-15. This Makefile can compile the sources into an XPCOM component.

A Makefile directs the Mozilla build system to build the sources and install them into the Mozilla dist/bin/components directory. To use the Makefile, run gmake to compile and install the component library file.

To test the newly compiled component, you can use xpcshell like you did for the JavaScript component. Example 8-16 shows a session with xpcshell that tests the new component.

8.2.7. Other Languages for XPCOM

Although most components available from XPCOM are written in C++, the XPConnect/XPCOM pairing can also accommodate other languages. Language independence is a goal of the XPCOM architecture. Currently, implementations for Python (PyXPCOM) and Ruby (rbXPCOM) exist, with other language bindings being developed. In this respect, the Mozilla framework dovetails with one of the main trends in application development, which is to mix different languages in the development environment.

8.2.7.1. PyXPCOM: the Python binding for XPCOM

Python has emerged as a very popular programming language in the last couple of years. It even does some of the application work and other heavy lifting that were the province of C++. Mozilla now offers a Python "binding" similar to the XPConnect binding for JavaScript that allows you to write application code in Python, compile it in XPCOM, and make it available like you would any C++ component in the Mozilla application framework. As with other XPCOM programming languages, you must create an implementation file (in Python) and an interface file (in IDL), as shown in Examples 8-18 and 8-19, respectively.

The terms and constructs for Python components are similar to those of C++. In the implementation, you need to import components from the XPCOM module to access the standard public members. The syntax is the same as that for importing any regular Python library:

from xpcom import components

The IDL for a Python implementation of an XPCOM component can be identical to one for a JavaScript- or C++-based component (which is the point of XPCOM, after all). As in any component, your IDL needs to include nsISupports.idl and declare itself as scriptable with a unique UUID:

[scriptable, uuid(6D9F47DE-ADC1-4a8e-8E7D-2F7B037239BF)]

JavaScript accesses the component in the same way, using classes and interface members of the component's interfaces to set up an instance of the component:

Components.classes["@foo.com/appSysUtils;1"].
    getService(Components.interfaces.appISysUtils);

With these foundations, and assuming that you have to have a Python distribution on your system that Mozilla can access, you are ready to go! Example 8-18 shows a complete implementation of a PyXPCOM component. This file needs to be saved with a .py extension and put in the components directory and registered like any other component.

The special attributes defined in the appSysUtils class correspond to the special identifiers you must use in XPCOM to make your code a reusable component (see Section 8.1.5, earlier in this chapter). Table 8-3 describes these attributes.

Example 8-19 is the IDL file you also need to create a Python component.

Finally, Example 8-20 shows how this component might be used in script -- for example, in a function you define for an event handler in the XUL interface.

The component is a small system utility that checks the read/write permissions status of a file on the local filesystem. The JavaScript uses it to display a visual notifier of the status in the UI. In this case, the DOM's rwcheck node refers to a checkbox. It's easy to imagine this component being extended to do other things, such as getting information about a file (the Stat stub is in the IDL). The source code, samples, and documentation for PyXPCOM are located in the Mozilla tree at mozilla/extensions/python.

8.2.8. XPCOM as an Open Cross-Platform Solution

XPCOM can be an entire book in itself. This chapter has merely touched upon the role it plays in Mozilla application development. Understanding the basics of this framework is vital to understanding the very foundation of Mozilla's componentized architecture.

Although other component-based systems exist on various platforms -- MSCOM for Microsoft or a CORBA system for GNOME, for example -- if you want to write truly cross-platform component-based applications, then XPCOM is the best tool for the job. It can be deployed on any platform Mozilla is ported to, and can be scripted by using JavaScript or Python.

Above all, XPCOM is entirely open source, so there are no costs associated with it, no proprietary secrets in how it's put together, and you have various software licenses to choose from. Although XPCOM has become a solid framework, its developers are still making improvements and uncovering and fixing bugs. However, XPCOM offers tremendous flexibility as a software development framework and the Mozilla community is an excellent technical support resource for all technologies covered in this book.