Chapter 8. XPCOM
This chapter provides ahigh-levelintroduction to XPCOM component technology. XPCOM can be difficult tomaster, but after reading this chapter, you should have a good senseof what it is and the important part it plays asMozilla's core technology. You should be able tofind and use existing scriptable components in your own applicationsand create a simple XPCOM component by using JavaScript or C++.
XPCOM permits a reusable code module to be globally accessible to aMozilla-based application. You do not need to worry about includingexternal source files in your application distribution and you candistribute components by using XPInstall. This type of architecturemakes the development of core application services flexible andentirely modular.
The section Section 8.2.1 lets youcreate an interface from start to finish -- writing theimplementation for that interface, compiling it into a type library,registering it with Mozilla, and then testing the new component. Oneadvantage of using XPCOM is that you can create multipleimplementations for a single interface; following the JavaScriptcomponent section, we will take the same nsISimpleinterface and implement it in C++ as well.
The section Section 8.2.5 includessome techniques and programming tasks that are particular to C++components, such as handling return values and generating headerfiles and useful macros. The section Section 8.2.7 introduces the XPCOMbindingsforthe Python language (pyXPCOM). First, it provides an overview ofXPCOM and how it relates to other technologies used in Mozilla.
8.1. What Is XPCOM?
XPCOM is Mozilla's cross-platform component objectmodel. Although it is similar to Microsoft's COMtechnology, this chapter points out some important differences.
Essentially, when you program in a component-based environment, youdo one of three things: you create a new component using existingcomponents, write a component that implements other components, andestablish interdependencies and a service network.
8.1.1. What Is a Component?
You've already seen components used in thisbook.In some cases, you may have used the services of Mozilla componentswithout knowing it -- for example, when you created a XUL treewidget in the section Section 3.4.2in Chapter 3, and used its built-in layout andview capabilities. Some of this functionality is defined in aninterface called nsITreeView, which providesspecific methods and properties for a XUL tree, persisting its state,row, cell, and column properties, navigation, and other objectmetadata used in a tree object. Behind the scenes,you'll find an XPCOM-instantiated tree view objectwhere methods and properties associated with the XUL element areaccessed via DOM > JavaScript >XPConnect > XPCOM layers.
A component is a reusable or modular piece of code that implements aclearly defined interface. In Mozilla, this code can exist as asingleton service or an object instance. A singleton service is anobject instance that is created only once and then used by other code(usually called "callers,""clients," or"consumers"). An object instance isan object that is instantiated once or many times. Components arewritten as classes that typically have member variables and methods.The basic purpose of a component is to implement a clearly definedset of APIs that exist in a public interface. The interface existsseparately so that the implementation is abstracted away, and it canbe changed without affecting the interface or breaking binarycompatibility. When interfaces are deployed in a productionenvironment, they are frozen, which means they are held in animmutable state -- theoretically for as long as the applicationexists. While MSCOM provides a component-based programming model onMicrosoft platforms, XPCOM provides it on all platforms where Mozillais available.
Example 8-1 shows how simpleusing XPCOM components can be.In two lines, an XPConnect-wrappednsIBookmarksService object is instantiated, andone of its methods is called, providing easy access to this XPCOMcomponent from JavaScript.
Example 8-1. Using an XPCOM object in script
// create a bookmark service object in JS var bmks = Components.classes["@mozilla.org/browser/bookmarks-service;1"]. getService(Components.interfaces.nsIBookmarksService); // call one of the object's methods: // flush the bookmarks to disk if they've been touched. bmks.Flush( );
As you can see, the assignment of an XPCOM object to the variablebmks takes only a single line. Once you arecomfortable using XPCOM from JavaScript, you can use any ofMozilla's scriptable interfaces in your application.Once an object like bmks is created, as in Example 8-1, it can be used to call any method in thensIBookmarksService interface, of whichFlush( ) is an example.
8.1.2. XPConnect and the Component Object
As shown the previous example, the XPCOM object is called andinstantiated from script. For an interpreted language like JavaScriptto call and instantiate it, a bridge must bind JavaScript types toXPCOM types. These type bindings are part of a technology calledXPConnect.
In XPConnect, XPCOM interfaces, classIDs, andprogIDs are stored as global JavaScript objectsand properties that can be manipulated directly through a top-levelobject called Components. This object accesses any component that is declared"scriptable" in an XPCOM IDLinterface. Through the Components object, you canaccess and use the services that these interfaces provide. TheComponent object's top-levelproperties and methods include:
-
QueryInterface
-
A method used to match an interface with a desired implementation.The implementation can be in C, C++, JavaScript, Python, and otherlanguages for which appropriate bindings are created. You can havemultiple implementations for an interface. ThroughQueryInterface, you can ask for and assign thedesired interface to its implementation. Each XPCOM object needs toimplement QueryInterface in order to return aninstance of that object's class:
js> var clz = Components.classes['@mozilla.org/file/local;1']; js> var inst = clz.getService( ); js> inst.QueryInterface(C.interfaces.nsILocalFile); [xpconnect wrapped nsILocalFile @ 0x81b7040]
-
interfaces
-
A read-only object arraycontainingall the interfaces declared scriptable in the IDL file. The objectname has the same name as the interface it represents.
Components.interfaces.nsILocalFile
The source file for this particular interface, for example, isnsILocalFile.idl. This XPIDL compiler compilesthis file to produce a cross-platform binary type library,nsILocalFile.xpt, which contains tokenized IDLin an efficiently parsed form.
-
classes
-
A read-only array of all the XPCOMcomponent classes indexed by the ProgID (orhuman-readable name) of the component class. Theclasses object has these properties associatedwith it:
toString Returns the string progID. QueryInterface Used to QI this interface. name Returns the string progid name. number Returns the string components uuid number. valid Boolean verifies if the instance is valid. equals The Boolean used to match identical instances. initialize I don't know what this does. createInstance Will create an instance of the component; you can have many instances. getService Will instantiate the component as a service; you can have only one instance of a service.
-
classesByID
-
The same as classes, except this time the array is indexed by the"canonical" or"well-established" form of theirCLSID:
Components.classesByID['{dea98e50-1dd1-11b2-9344-8902b4805a2e}'];
The classesByID object has the same propertiesobject associated with it as the class object. Theproperties are also used in the same way:
toString QueryInterface name number valid equals initialize createInstance getService
-
stack
-
A read-only property that represents a snapshot of the currentJavaScript call stack. JavaScript handles each code interpretationone call at a time and then places that code onto a call stack. Thisproperty can be used for recondite diagnostic purposes:
js> var C=Components; js> C.stack; JS frame :: typein :: <TOP_LEVEL> :: line 2 js> C.stack; JS frame :: typein :: <TOP_LEVEL> :: line 3
-
results
-
An object array ofnserror results:
Components.results.NS_ERROR_FILE_ACCESS_DENIED; 2152857621
-
manager
-
A reflection of the XPCOM global native component manager service.Using the component manager is the only way for a component to actuallybe created. It uses the components factory to create an instance ofthe class object.
ID
-
A constructor used for a componentwritten in JavaScript This component needs to register itself withthe component manager by using its own nsID (an IDthat is not already registered and thus does not appear inComponents.classes).
Exception
-
A JavaScript constructor used tocreate exception objects. When implementing XPCOM interfaces inJavaScript, these exception objects are the preferred types ofexceptions. When an XPCOM exception is thrown in your JS code, ittakes the form of an Exception object that hasproperties associated with this object. Exceptions are usually caughtin a "catch" block.
Constructor
-
A JavaScript constructor object that constructs new instances ofXPCOM components:
js> var File=new Components.Constructor( "@mozilla.org/file/local;1", "nsILocalFile", "initWithPath");
The interface nsILocalFile and the methodinitWithPath are optional. This example createsand initializes the nsILocalFile component.
-
isSucessCode
-
A function that determines if the results code argument issuccessful. It takes an argument of nsresult andreturns the Boolean values true or false:
js> Components.isSuccessCode(Components.results.NS_OK); true js> Components.isSuccessCode(Components.results.NS_ERROR_FAILURE); false
The methods and properties of the Componentsobject listed above provide the only means to instantiate and accessXPCOM objects from JavaScript. They are found often in the Mozillacodebase. In the sections that follow, they will be used frequently.
8.1.3. XPCOM Interfaces and the IDL
All XPCOM interfaces are definedwith the Interface Definition Language(IDL). IDL provides a language-neutral way to describe the publicmethods and properties of a component. Mozilla actually uses amodified, cross-platform version of IDL called XPIDL to compileinterface source files.
The separation of interface and implementation is a key distinctionof COM programming. If the application programming interface (API) isabstracted from the implementation language and then frozen,consumers of that API will receive a guaranteed, established contractwith the interface that ensures it will not be changed. This isperhaps the main reason why COM was invented: to maintaincompatibility on a binary level so the client code can find and usethe library it needs without worrying about linking to it. To makethis sort of modularity possible at runtime, IDL interfaces arecompiled into binary files called type libraries, which are describedlater in the section Section 8.1.4.
8.1.3.1. Interfaces versus components
It is important to understand thatmost XPCOM components implement at leasttwo interfaces. Like COM, each component needs theQueryInterface, AddRef, andRelease functions to be available as an XPCOM object. These methodsare derived from a basic interface callednsISupports, which isthe XPCOM equivalent to MicrosoftCOM's IUnknown, shown in Table 8-1.
Table 8-1. The IUnknown interface
Name | Type | Description | Parameters / return value |
---|---|---|---|
AddRef | ULONG AddRef(void) | Increments the reference count on the COM object. | Returns: int, which is the new incremented reference count on the object. Thisvalue may be useful for diagnostics or testing. |
QueryInterface | HRESULT QueryInterface(/* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject) | Retrieves a pointer to the requested interface. | Parameters: iid, which is an [in] identifier of the requested interface. ppvObject, which is an [out] pointer to the interface pointeridentified by iid. If the object does not support this interface,ppvObject is set to NULL. Returns: HRESULT, which is the standard HRESULT value. |
Release | ULONG Release(void) | Decrements the reference count on the COM object. | Returns: int, which is the new decremented reference count on the object. Thisvalue may be useful for diagnostics or testing. |
Tables 8-1 and 8-2 illustrate the minor differences betweenMicrosoft's nsIUnknown andMozilla's nsISupports rootinterfaces. The usage is covered in detail throughout this chapter.
Table 8-2. The nsISupports interface
Name | Type | Description | Parameters / return value |
---|---|---|---|
AddRef | NS_IMETHOD_(nsrefcnt) AddRef(void) | Increases the reference count for this interface. The associated instance will not be deleted unless the referencecount is returned to zero. | Returns: The resulting reference count. |
QueryInterface | NS_IMETHOD QueryInterface(REFNSIID aIID, void**aInstancePtr) | A runtime mechanism for interface discovery. | Parameters: param aIID [in], which is a requested interface IID. param aInstancePtr [out], which is a pointer to an interface pointerthat receives the result. Returns: NS_OK if the interface is supported by the associated instance;NS_NOINTERFACE if it is not; and NS_ERROR_INVALID_POINTER ifaInstancePtr is NULL. |
Release | NS_IMETHOD_(nsrefcnt) Release(void) = 0; | Decreases the reference count for this interface. Generally, if thereference count returns to zero, the associated instance is deleted. | Returns: The resulting reference count. |
8.1.3.2. Root interfaces
QueryInterface, Addref, andRelease are required methods that are implemented byevery component. QueryInterface matches a specificinterface with its implementation class module.Addref and Release are methodsused for reference counting. When an instance of a component iscreated, one or more pointers may reference that object. For eachreference, a count is incremented by one. When a reference is nolonger used, Release is called to decrement thecount. You must hold a reference count to ensure that no pointersreference an object after it is deleted. When pointers try to accessobjects that are deleted, the application core dumps. Referencecounting can get tricky, which is why smart pointers manageAddref and Release for you, asdescribed in the later section Section 8.2.4.
Defining QueryInterface,Addref, and Release every timean interface is created is clearly not very efficient. Instead, thesemethods are defined in the base interface callednsISupports. All interfaces inherit from thismother of all interfaces and don't need to redefinethese three basic functions.
XPIDL supports the C style syntax preparser directive#include to include other IDL files -- notunlike MSCOM, which uses the import statement. At the top of any IDLfile that you create, you need to includensISupports:
#include "nsISupports.idl" interface nsISimple : nsISupports { readonly attribute string value; };
8.1.3.3. The XPIDL compiler
An IDL compiler is a tool thatcreates a binary distribution filecalled a type library from an interfacedescription source file. Since support for many different platformsis a requirement for Mozilla, a modified version of the libIDLcompiler from the Gnome project is used. This variant is called theXPIDL compiler and is primarily used to compileMozilla's own dialect of IDL, conveniently calledXPIDL. The XPIDL compiler generates XPCOM interface information,headers for XPCOM objects, and XPT type libraries from which objectsmay be accessed dynamically through XPConnect. It can also generateHTML files for documentation and Java class stubs. Another feature ofthe XPIDL compiler is the option to generate C++ code stubs. Thisfeature creates nearly all the declaratory C++ code you need when youstart a new project, which makes XPIDL useful as a coding wizard thathelps you get started. Code generation is covered later in thischapter in the section Section 8.2.5.
The XPIDL compiler is located inxpcom/typelib/xpidl/ in the Mozilla sources. Ifyou built Mozilla, you can add this directory to yourPATH:
$ PATH=$PATH:/usr/src/mozilla/xpcom/typelib/xpidl
Using the compiler is fairly easy. If you use the help command, youcan see the usage syntax and other basic information about thecompiler:
$ ./xpidl --help Usage: xpidl [-m mode] [-w] [-v] [-I path] [-o basename] filename.idl -a emit annotations to typelib -w turn on warnings (recommended) -v verbose mode (NYI) -I add entry to start of include path for ``#include "nsIThing.idl"'' -o use basename (e.g. ``/tmp/nsIThing'') for output -m specify output mode: header Generate C++ header (.h) typelib Generate XPConnect typelib (.xpt) doc Generate HTML documentation (.html) java Generate Java interface (.java)
8.1.4. XPCOM Type Libraries
The key to the component architecture of XPCOM is the presence ofbinary-independent interface files that are used uniformly acrossplatforms, languages, and programming environments. These interfacefiles are compiled into .xpt files by the XPIDLcompiler. The Mozilla components subdirectory iswhere type libraries and modules are typically stored. If you createa cross-platform type library for your component, you must place itin this directory for it to be accessible to XPCOM.
8.1.4.1. Creating a type library file from an IDL interface
To create a (.xpt) typelib file, usethe flag -mtypelib with warning (-w) and verbose(-v) modes turned on. -o isused for the name of the output file and -I isused to specify paths to other IDL files you want to include. Tosuccessfully compile your interface, you must always point to thedirectory where nsISupports is located.
# include path to nsISupports.idl $ $XPIDL_INC = /usr/src/mozilla/xpcom/base #compile nsISimple.idl $ xpidl -m typelib -w -v -I $XPIDL_INC \ > -o nsISimple nsISimple.idl
The file created after compilation isnsISimple.xpt. It provides the necessary typeinformation about your interface at runtime. Typelib files enumeratethe methods of interfaces and provide detailed type information foreach method parameter.
8.1.5. XPCOM Identifiers
To simplify the process ofdynamicallyfinding, loading, and binding interfaces, all classes and interfacesare assigned IDs. An ID is a unique 128-bit number that is based onuniversally unique identifiers (UUIDs) generated byvarious toolssuch as uuidgen (which we will cover later inthis chapter). They are stored in the structure format defined below:
struct nsID { PRUint32 m0; PRUint16 m1, m2; PRUint8 m3[8]; };
To initialize an ID struct, declare it like this:
ID = {0x221ffe10, 0xae3c, 0x11d1, {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}};
One thing that gives XPCOM its modularity is the dynamic allocationof objects through the use of unique identifiers at runtime. Thissystem of canonical identifiers is used for interface querying andcomponent instantiation. Having an interface is important because itensures that an immutable binary holds a semantic contract definedfor a specific interface class.
The two types of identifiers used in XPCOM are the contract ID andthe class identifier. These identifiers are shuttled to the ComponentManager's createInstance( ) orthe Service Manager's getService() methods in order to instantiate a component.
8.1.5.1. The Contract ID
The program ID (progID), also knownas the Contract ID, is a uniquehuman-readable string. Example 8-2 shows variousprogIDs for different components. This example canbe used to instantiate an XPCOM component through the use of aContract ID.
Example 8-2. progIDs
// progID: @mozilla.org/file/local;1 var f = Components.classes[`@mozilla.org/file/local;1']; // progID: @mozilla.org/browser/bookmarks-service;1 var bmks = Components.classes["@mozilla.org/browser/bookmarks-service;1"]. getService(Components.interfaces.nsIBookmarksService);
8.1.5.2. The class identifier
The other type of identifier is theclassID,or CLSID. The interface and implementation are identified by this128-bit numerical identifier string:
// clsid: {2e23e220-60be-11d3-8c4a-000064657374} var f = Components.classesByID["{2e23e220-60be-11d3-8c4a-000064657374}"];
Using XPConnect, XPCOM interfaces,classIDs, andprogIDs are stored as global JavaScript objectsand properties and can be manipulated directly through the top-levelComponents object discussed earlier.
8.1.5.3. Generating identifiers
To obtain a UUID on Unix, you canusea command-lineprogram called uuidgen that generates a uniquenumber for you:
$ uuidgen ce32e3ff-36f8-425f-94be-d85b26e634ee
On Windows, a program called guidgen.exe doesthe same thing and also provides agraphical user interface if you'd rather point andclick.
Or you can use one of the special"bots" on IRC at theirc.mozilla.org server.
irc irc.mozilla.org /join #mozilla /msg mozbot uuid
This command makes the bot generate and return a uuid, which you canthen copy into your component source code. The information can thenbe used to uniquely identify your component.
8.1.6. Component Manager
One major goal of XPCOM modularizationis the removal of link-time dependencies,or dependencies that arise when you link libraries duringcompilation. The achievement of this goal allows you to access anduse modules at runtime. The trouble then becomes finding thosemodules and figuring out which of their interfaces you want to use.This problem is solved through the use of the Component Manager.
The Component Manager is a special set of component managementclasses and implementation classes that reside in object libraries(.dll, .so,.js, .py, etc.). Theseclasses also include factories, which let youcreate objects without having access to their class declarations.When you bind to objects at runtime, as you do in XPCOM, you needfunctionality like this to help you discover and use objects withoutlooking at their code. The Component Manageralso includes the Component Manager classitself, known as nsComponentManager, which is amapping of class IDs to factories for the libraries they contain. TheComponent Manager is responsible for the autoregistration of all newor add-on modules locatedin the components directory. Thisautoregistration happens behind the scenes and allows you to use newcomponents as they become available without having to register themyourself.
A component author first creates an interface file thatdefines all APIs that will be publicly available for a component. Thecomponent author then creates an implementation for the methods andattributes in a separate implementation class. For example, annsILocalFile interface may have annsLocalFile implementation class. Then a factoryor module is needed to abstract the implementation class, and reducecompile and link-time dependencies. It then creates instances of theimplementation class through its own implementation ofQueryInterface. For example:
// create an instance of the implementation class var f = Components.classes[`@mozilla.org/file/local;1'].createInstance( );
The variable f is assigned an instance of ansLocalFile implementation class using thensIFactory method createInstance(). To match the correct interface(nsILocalFile in this case) to theimplementation, you need a class instance to be created before youcan call on its member method QueryInterface( ):
// QI for nsILocalFile interface var f = f.QueryInterface(Components.interfaces.nsILocalFile);
Once you do this, the variable f is ready to usethe nsILocalFile interface to access the newlycreated instance of the nsLocalFile class fromscript.
Simply put, a factory or module is a set of classes used by theComponent Manager to register and create an instance of thecomponent's implementation class. A factory can makeits way into the Mozilla component repository in several ways. Themost direct is through using the Component Manager methodRegisterFactory( ), which supports two differentregistration mechanisms. The first mechanism, which takes a class IDand a pointer to a factory, can be used on factories that areactually linked into the executable. The second, which takes a classID and the path to a dynamically loadable library, can be used bothinside an executable at runtime and externally by using theaPersist flag to tell the repository to store theclass ID/library relationship in its permanent store. The ComponentManager discovers new factories or modules placed in thecomponents directory and queries those modulesfor the XPCOM components they provide. The name, contract IDs, andclass IDs are placed into a small component registry database forquick retrieval. The factory provides this information through asimple set of APIs required by every XPCOM module. Module creation,covered later in this chapter, describes the process through whichall components contain an implementation of a module or factory.
8.1.7. Getting and Using XPCOM
Mozilla is a client application that implements XPCOM, so everythingyou need to use or build new XPCOM components is already included inthe source code and/or the binaries. Whenever you use the JavaScriptComponents object, as described earlier, you useXPCOM.
If you'd rather not build the entire Mozilla browserand you have no interest in existing Mozilla components or the largefootprint that comes with an entire distribution, then standaloneXPCOM is for you. Topullthe XPCOM source on Unix using Mac OS X orcygwin on Windows, invoke the followingcommands:
cvs -z 3 co mozilla/client.mk cd mozilla gmake -f client.mk pull_all BUILD_MODULES=xpcom
To build the XPCOM Stand Alone version, type:
configure --enable-modules=xpcom gmake
When you buildstandaloneXPCOM, the directory xpcom/sample contains thesource code for a sample application and ansTestSample component. The sample applicationbuilt from these sources, also callednsTestSample, is installed in the Mozillabin directory. libsample.so(Unix), which is the component that the sample application tries toinstantiate, should have been installed inbin/components. To run the test that indicateswhether standalone XPCOM is installed successfully, change to themozilla/dist/bin directory and run the followingcommands:
./run-mozilla.sh ./nsTestSample
You should see the following output. If you do not, there is aproblem with the installation of standalone XPCOM:
Type Manifest File: /D/STAND_ALONE_XPCOM/mozilla/dist/bin/components/xpti.dat nsNativeComponentLoader: autoregistering begins. nsNativeComponentLoader: autoregistering succeeded nNCL: registering deferred (0) Inital print: initial value Set value to: XPCOM defies gravity Final print : XPCOM defies gravity Test passed.
Using standalone XPCOM is a powerful way to use the Mozilla frameworkof cross-platform COM. Even if you're just hackingon Mozilla, standalone XPCOM is a great way to learn about and useXPCOM for application development.