Test

January 13, 2009

ABCD


Building Games for the iPhone

January 4, 2009

6737CC90-8AC1-42AC-833F-0104E6CFE930.jpg

Almost overnight a lot of apps have popped-up for the iPhone and many of them are games. Quite a few of them look like they were developed by hobbyists over a weekend (not that there’s anything wrong with that). But developing a quality, professional game for the iPhone is still a time-consuming and difficult task.

Once you’ve settled on the game concept, you have to create the play logic, then generate the graphic assets and multimedia. And somewhere along the line you actually have to write the code to make it all happen.

To build a serious iPhone game you currently have several choices:

1. Create a custom one-off app in Objective-C (or C++):

This gives you the most control but it means your game is pretty much going to stay on the iPhone. No desktop version. No Wii. Just iPhone. That might be fine, but if you’re investing tons of hours in content creation and game design, you may want to think hard about whether it makes business sense to leverage all that work and hit more platforms. Building a custom one-shot app also means that all your development effort is only for that one game. Instead, you may want to…

2. Develop a general-purpose iPhone gaming engine:

Where the gaming logic and media content are kept separate and defined in configuration files. This way, you can get two, three, maybe even 100 bangs for your buck. Hopefully, the configuration language is expressive enough so you can build a whole class of apps, not just the same thing over and over. You should be mindful, however, that the iPhone SDK prohibits use of scripting languages, so you can’t embed a Javascript, Lua, or Python interpreter into your code. If you don’t have the know-how or are short on time, you may want to…

3. License a third-party gaming engine:

This takes you away from the pleasure of writing raw Objective-C code (I’m not kidding — it actually is a lot of fun) but it also gives you support tools like 3D modelers, asset managers, physics engines, networking, etc. so you can focus on the high-level logic instead of low-level coding. Most also support some sort of scripting, but get around the SDK restriction by compiling it into executable code.

I have worked on several Objective-C-based iPhone apps so far and developed a custom animation engine for a client, so I have squarely followed options #1 and #2. But I also have ideas for quick, fun entertainment/game apps that I’d like to whip out quickly without spending months on building a custom animation engine. I’d rather spend my time on polishing the game logic and generating nice looking multimedia assets instead of working around iPhone Core Animation’s strange quirks (please, don’t get me started).

So I decided to look around and see what’s out there and came up with the following. To be fair, I haven’t had time to dig too deeply into each one, but as a public service I figured I’d share what I’ve found so far. If I’ve missed any other platforms or made any factual errors, please feel free to post a comment and I’ll issue an update.

Here they are (in alphabetic order):


ShiVa (with the iPhone Authoring Tool) from StoneTrip.


Torque Game Builder (with the iPhone SDK addition) from GarageGames.

Unity iPhone.

In terms of features it’s hard to tell them apart from their spec sheets. They all feature 2D or 3D graphics, support a variety of media and content (including shaders, sound, movies, etc), handle physics and collision detection, and playback audio and video. On the iPhone, there’s also support for the accelerometer (for tilt moves). All three engines support server-based multi-user playing which requires licensing their servers (or their hosting services). It’s not clear at this point whether they support WiFi-based Bonjour peer-to-peer networking which the iPhone and Touch both support.

What differentiates the three engines is what other platforms they support and their pricing model, so let’s dig into that.

ShiVa

BF47FA83-4E48-480B-8F6B-8CB3625751BA.jpg

ShiVa comes in three versions, PLE, Advanced, and Unlimited (here’s the feature comparison table). The development platform runs under Windows (or Parallels on the Mac). The PLE version is free and allows you to create an application, but you can’t publish the output commercially. For publishing you’ll need the Advanced or Unlimited versions. Advanced costs €169 Euros (approximately $235 at today’s exchange rate) whereas Unlimited will set you back €1,499 Euros (approximately $2080).

The main difference between the two seems to be that the Unlimited edition has additional benchmarking and optimization tools and supports team development. There is no extra cost for output to iPhone (and it looks like they intend to support Windows Mobile and Symbian). You can also target your game so it can run in a browser, but it requires the user to download and install a plugin. A standalone desktop app generator lets you target Windows, Mac OS X, and Linux. However, you’ll likely need to repurpose your media to fit the different screen sizes.

As far as console platforms are concerned, not much there yet.

To support multi-player mode, you’ll need to license the Ston3D Server which comes in PLE, INDIE and PRO flavors. PLE is free but is limited to a single application and 6 simultaneous users. Clearly, it’s intended only for development and testing. The INDIE version runs on Windows, Ubuntu, and FreeBSD, but is limited to 64 sessions (game instances) and 4 sessions per server. It runs €359 Euros (approximately $500) and the PRO server without the session limitations runs €599 Euros (approximately $832).

There are also extra server-side features like managed hosting, payments module, and direct messaging to the user (via SMS, MMS, and email).

Torque

A678EE71-8C3E-4ADB-A52C-C33C20A8A010.jpg

GarageGames offers a dizzying array of products and Torque variations, targeted at anything from simple 2D to networked 3D games. The basic 2D package is the Torque Game Builder which runs $100 for Indie apps (those earning less than $250K per year) or $495 for Commercial version. The Pro version also gives you access to the source code for both the engine and the editing tools ($250 for Indie, $1250 for Pro).

If you want 3D support then there’s Torque Game Engine ($150 Indie, $749 Commercial). In both cases, you get a lot of tools that support building levels, media, sprites, etc. and take care of a lot of the low-level grunt work for you.

But that’s not all, Bob. There’s also Torque Engine Advanced ($295 Indie, $1495 Commercial). This gets you all the tools to develop advanced 3D games for consoles and desktops. To deploy your game to a console, you’ll want to look at Torque Wii or Torque 360 (for the XBox 360). License fees for these have to be negotiated.

But it’s the iPhone we care about and to output there, you’ll want Torque for the iPhone. First you’ll need a license to one of the existing ‘builder’ tools (Tool Builder for 2D, or Engine for 3D). for the 2D version, you pay an additional $500 for an Indie license. That lets you publish a single iPhone title. Each additional title you want to publish requires an additional $100 license fee. You also have to show the GarageGames splash screen when the game starts and mention them in the game credits (and app web-site). 3D game support on the iPhone hasn’t been released yet so there’s no price listed.

Want server-based networking? The basic server is open-sourced under GPL. If you want to use it in a commercial app, however, the cost is $295 for Indies and $995 for Commercial apps (consoles are separate). This is for games delivered on Windows, Mac, or Linux. It’s not very clear if networking is supported on the current iPhone version, but I imagine it’ll be there soon.

Unity

6EFCB662-A1F3-4251-A505-B91BC8240FA0.jpg

Unity supports 2D and 3D content with a visual editor to help you develop and design your game content. The underlying scripting technology is based on C# and Javascript but their iPhone Publishing product spits out an XCode project that they claim ‘just works,’ compiling the scripting code into fast ARM assembler code (and thus avoiding the iPhone SDK’s edict against built-in scripting languages).

Under Unity, the Editor is the main point of creating apps. You visually adjust parameters and get live previews, then create scripts to handle game logic. In iPhone ‘preview’ mode, you adjust settings on your desktop screen inside the visual editor and watch it update live on the target test iPhone. It’s a very cool way to quickly adjust and position your objects and verify that they look right on the iPhone screen.

To develop Unity apps, you need the editing system ($199 for Indie developers earning $100K or less — with free 30-day eval, or $1499 for Pro) which lets you generate output for Mac, Windows, browser plugin, and OS X dashboard widget. To output to the iPhone you can got for the Basic iPhone license for $399 for Indie developers and requires showing the Unity splash screen, or $1499 for the Advanced license. Wii/WiiWare output is separate and carries a hefty license fee ($15K-$30K per title).

The Advanced edition also gets you .NET sockets. This means that you can write your own back-end server and aren’t locked into theirs, but you don’t necessarily get Bonjour/WiFi support. You can also stream assets on-demand (which requires an asset-server client license for $499) but I can’t imagine anyone wanting to stream assets unless the user was on WiFi.

Risks

There are inherent risks with using a third-party middleware. Will the platform continue to be supported? Are they actively fixing bugs? What happens if they go out of business and you want to continue developing your app? If these are concerns, then you may want to consider Torque’s Pro versions since they come with source code.

On a resource-restricted platform like an iPhone there’s also the matter of having a whole extra layer of runtime between your app and the OS. If your app is going to be pretty media-heavy you may want to roll your own and keep tight control over memory use.

Don’t be scared by these caveats. For certain classes of games these engines will amply make up for the risks by letting you concentrate on content instead of engine technology and getting your app out that much sooner. If used properly, they can also act as ‘force multipliers’ if you are an individual developer or a 2-3-person team. With these tools, you can rapidly create cool apps that would otherwise require a small army of coders and designers.

Which one?

C7D0609E-537D-47B3-AB33-C88AC20A1260.jpg

Which one you choose will depend primarily on what features you need, so the first thing I suggest is to download and try out each package (here are direct download pages for ShiVa, Torque, and Unity). All three have free or eval versions and offer Indie pricing for small developers. If your app turns into a big hit and brings in enough revenue, it’s easy to justify the cost of the Pro or Advanced licenses.

If you intend to eventually move to the XBox then Torque is the only way to go. If Wii is where you might be heading, then it’s Torque vs. Unity. All three platforms support standalone desktop apps. I’m not sure with the prevalence of Flash in the browser if anyone’s willing to download and install a browser plugin just to run an application, so I’ll call web-based delivery a wash.

If licensing fees are a concern, then you may want to go with ShiVa. If you only need 2D support, then Torque may work for you (although their iPhone per-app licensing fee is a little too strange for my taste). Mac-only developers will want to look at Unity or Torque Engine Advanced . All others require Windows (but may work under Parallels or VMWare).

iPhone-only features

03371ACA-50E6-4819-9DC0-C142D27190DB.jpg

At this point, nobody seems to support peer-to-peer Bonjour-based networking on the iPhone. Quite a lot of games support that feature. Unlike a Nintendo-DS which allows two players to form an ad-hoc network just by sitting near each other, this only works when all the players are on the same WiFi subnet. It works pretty well when players are in the same room or dorm floor and doesn’t require going out to a central server. It’s especially handy in places where data access is metered and hitting a central server through the cell network can get expensive. Hopefully Bonjour support is something that will be supported soon.

All three engines appear to have decent support for the accelerometer but no mention yet of other iPhone-only features like multi-touch, GPS, or camera. One other thing to keep in mind is that all the license terms and prices listed apply to games only. If you want to develop a social networking or business app you may have to negotiate a separate license.
Bottom line

Remember that regardless of platform, you’ll need to sign-up separately for Apple’s iPhone development program (a $99 cost for individuals) to get a distribution certificate. You’ll also need to do the actual legwork of submitting the application to the app-store. And once the app is out, there’s the matter of marketing and promoting so your app stands out against all those other ones out there.


How to use Subversion with Eclipse

December 30, 2008

You’re going to need to download and install Eclipse (see Resources) to follow along. Downloading the Eclipse SDK package for your platform will give you the base Eclipse IDE (referred to as the Eclipse Platform), as well as the Java™ Development Kit. If you plan on working with C/C++ (as I tend to), visit the C Development Tooling (CDT) Web site and install the CDT using the update manager (using the update manager is described in the next section).
You’ll also need access to a Subversion repository. If you need to set one up, you can find excellent documentation at the Subversion Web site (see Resources). For demonstration purposes, I’ll show you how to check out the Subclipse project and work with projects in a repository on my LAN.

Adding Subclipse to Eclipse

Subclipse is a project to add Subversion support to the Eclipse IDE. We’ll use Eclipse’s update manager to add Subclipse to our Eclipse IDE. From the Help menu in Eclipse, choose Software Updates > Find and Install to open the update manager.

Figure 1. The Eclipse update manager

85976543-8EC1-4878-B75D-295AFD0F39D7.jpg

In addition to using this to look for software updates, we can use the update manager to find and install new features, such as Subclipse. Be sure that Search for new features to install is selected, then click Next to continue. Eclipse displays the next update manager panel.

Figure 2. Update manager sites

C783A695-5A69-40BE-A2A5-295212D880B3.jpg

Since we’re after a specific feature, un-check the existing sites, then click New Remote Site to display the New Update Site dialog (see Figure 3). We’ll use this to add the Subclipse update site to the list.

Figure 3. Adding a new update site

942D6B18-A834-4899-A5DE-068D82F283B5.jpg

Enter whatever you want for the Name (Subclipse is a good choice) and enter the following for the URL: http://subclipse.tigris.org/update_1.0.x (the current Subclipse update site). Click OK to add the Subclipse update site to the list in the update manager.
Click Finish in the update manager window to begin searching for new features. In this case, the new feature we’re after is Subclipse. After a few moments, the update manager’s search is complete, and it displays the search results.

Figure 4. New features we can install

F5D9C30D-5FBD-4FD2-B1B7-FDC2BDDBCAF3.jpg

Check Subclipse (you can click the disclosure triangle to see what exactly is included in this feature), then click Next to view the feature’s license terms. Accept the terms, then click Next to review the features you’ve chosen to install. Click Finish to download and install Subclipse.
The update manager downloads the Subversion components. Before installing anything, Eclipse will warn you that the features aren’t digitally signed (Figure 5). This is your last chance to cancel the installation. Click Install All to continue the installation.

Figure 5. Subclipse isn’t digitally signed

6FDD09FA-0EDA-4E4D-A04A-67F693D56151.jpg

Once Subversion has been installed, Eclipse warns you that you might need to restart the IDE to activate the new features (see Figure 6). Restart Eclipse, just in case.

Figure 6. Restart Eclipse after installing new features

6E46CA71-9A79-4D0C-A58C-067FDAF311A8.jpg

When Eclipse comes back up, Subclipse is installed and ready to go.
If you’re running Eclipse on Mac OS X or Linux®, you may need to install the JavaHL library, which is described in the Troubleshooting section of the Subclipse FAQ (see Resources). Do this before you continue trying to use Subclipse.
A quick test
It’s always nice to test a new feature once you’ve finished the installation; we’ll try checking out a copy of Subclipse from their Subversion repository to make sure it’s been properly installed.
From Eclipse’s File menu, choose Import to display the import manager (see Figure 7). Choose Checkout Projects from SVN, then click Next.

Figure 7. The import manager

F974FDCE-D7FC-48F9-BAF7-57C13587BABD.jpg

On the Select/Create Location panel (see Figure 8), we need to create a new location (since we don’t have any configured yet), so click Next to continue. If the Next button is disabled, switch to the Use existing repository location option, then back to Create a new repository location to enable the Next button.

Figure 8. Creating a new repository location

66ACC506-7867-486C-9C3A-4EA5E780534E.jpg

In the next section (see Figure 9), add the repository URL (http://subclipse.tigris.org/svn/subclipse/) to the Url field, then click Next. After a moment, Eclipse prompts you for user ID and password. If you don’t have an account on the Subclipse site, enter guest for the user ID and a space for the password, check the Save Password box, and click OK.

Figure 9. Add the repository URL

B2529AE3-3A87-49B0-B2A5-D95DB6BAC4D3.jpg

Eclipse displays the folders in the Subclipse repository (see Figure 10). Expand the trunk and choose the subclipse folder, then click Finish to check out your own copy of the Subclipse project’s source code. Since you have no idea what this is, choose Simple > Project when the New Project wizard prompts you.

Figure 10. Subclipse repository

93D46906-AB25-4C44-A72B-83A18B2F7A62.jpg

Basic Subversion operations

At this point, we’ve installed Subclipse successfully, which added support for Subversion servers to our Eclipse setup, and we’ve tested Subclipse by downloading the current Subclipse source code from the repository. Now we should look at doing something with our own code and our own Subversion repository.
Before I show you how things work with Subversion, I’ll tell you a little bit about my repository. It’s hosted on a machine called dogma on port 8000, and I’ve created a new developerworks repository for code associated with my developerWorks articles. I’m going to put my projects directly in the root of the repository. Other repositories often have folders named trunk, tags, and branches off the root, for development versions, tags, and branches, but I don’t expect to need to worry about tagging or branching the developerWorks article code.
I’ve added two projects, forkWork and threadWork, from my first developerWorks article. My Eclipse workspace (see Figure 11) also contains three other projects from developerWorks articles (getopt_demo, getopt_long_demo, and readdir_demo).

Figure 11. My Eclipse C/C++ projects

4B4DDBC0-640E-4B2A-95A0-2B36684EA575.jpg

Now we’re ready to get to work.
Adding a project to the repository
To add a new project to your Subversion repository, right-click the project (in any of Eclipse’s project views or the Navigator view) and choose Team > Share Project from the menu. Eclipse displays the Share Project dialog.

Figure 12. The Share Project dialog

E170724A-B2D1-4417-9C93-F7245A0B9A36.jpg

Select SVN from the list of repositories currently supported by your Eclipse, then click Next. The next dialog (see Figure 13) lets you choose an existing repository location, or you can create a new one.

Figure 13. Selecting a repository location

E45F4524-C9BC-458B-9E9D-8AEFF10FE620.jpg
If your repository is already listed (as you can see, I’ve added mine), select it, and click Finish. If your repository isn’t listed, add it (see A quick test for instructions) and continue. Eclipse creates a new directory in the repository with the same name as your project, and displays a list of all files and folders in the project.

Figure 14. Adding a project’s contents

876AD99F-8080-4924-99C6-2A41DCDC109E.jpg

Enter a suitable comment describing this project in the top field, then click Select All to check all of the files from the project. Click OK to check in your project and transmit its current state to the Subversion repository.
Subversion’s commands and output are displayed in the Console view, usually found at the bottom of your Eclipse window, if you want to see exactly what Subclipse did with your project.
Updating a project
One of the key features of a version-control system is the ability for other developers to continue development and commit their changes whenever they’re ready. To download these changes and integrate them with your local copies, you need to update the project.
Right-click on the project you want to update, then choose Team > Update from the menu. Eclipse retrieves any changes from the repository and attempts to merge them with your local copy.
Adding a file or directory
If you add a file to your project (see Figure 15), it’s not automatically part of version control — you need to specifically add it to the repository. In the screenshot, you can see that I’ve added a ReadMe.txt file to the threadWork project.

Figure 15. Adding a new file

D576876A-1D6E-40D1-8A55-750FCFFB4FC0.jpg

Right-click the new file, then choose Team > Add to Version Control. That’s it! The next time you commit your changes in this project to the repository, the new file will also be checked in.
Deleting a file or directory
If you’ve added a file to the repository that’s no longer relevant to your project, you can easily delete it. Right-click the file, then choose Delete. No need for the Team menu, Subclipse flags the file for deletion automatically and removes it from your project. The next time you commit your changes to the repository, the file is deleted.
Renaming a file or directory
To rename a file or directory under Subclipse’s control, right-click it, then choose Rename. Type the item’s new name in the entry field and click Enter. The file is renamed in the project, and the rename operation (an Add for the new name, and a Delete for the old one) is queued for your next commit. In Figure 16 you can see the threadWork project after I’ve renamed main.c to threadWork.c, but before I’ve committed my change. Note the little blue plus sign Subclipse has added to the “new” file to indicate that it’s scheduled for addition in the next commit.

Figure 16. Renaming a file is atomic, even though it’s an add and a delete

9FEA762C-BC9D-40E0-AD82-7EDFDA880D15.jpg

Ignoring files
If your project generates files, or otherwise includes files that you don’t want to check in to the Subversion repository, you can tell Subclipse to ignore them. Right-click the file or directory you want to exclude from version control, then choose Team > Add to svn:ignore to display the Add to svn:ignore dialog.

Figure 17. Ignoring resources that don’t belong in version control

3CBA3B8F-AF54-44C2-AD32-0F65A2FE7081.jpg

Click OK to add this specific file to the svn:ignore property for the project’s directory. Choose Wildcard extension to ignore all files with the current file’s extension, or choose Custom pattern to add your own wild card to the ignore list. These changes to the ignore list will be added to the repository the next time you commit your changes.
Committing your changes
Once you’re happy with your changes to the project, you’ve made sure your code compiles, and you’ve tested your changes, you should commit them to the Subversion repository. This acts as a backup in case your workstation self-destructs, and it lets other developers update their local copies to include your changes.
Be sure to update your project (see “Updating a project”) before attempting to commit your changes. Right-click the project and choose Team > Commit from the menu. Eclipse displays the Commit dialog (see Figure 18), which summarizes your changes.

Figure 18. Committing your changes to the repository

DE98D914-B150-4BB6-B76B-6E8A21109845.jpg

If you look carefully, you’ll see a property change to the project’s directory (I’ve added to the svn:ignore property to keep certain files out of the repository) and that main.c was deleted while threadWork.c was added. That pair of changes actually represents one operation (a file rename).
At this point, you can deselect resources if you want to keep them out of the repository. This might be helpful if you’re partially finished work in one file, and don’t want to check in an incomplete change. Enter a suitable comment in the top text field, then click OK to check in your changes to the repository.

Summary

The Subclipse project integrates support for the Subversion version-control system with Eclipse’s excellent team project management features, which only support CVS servers out of the box. Using Eclipse’s update manager, it’s easy to add Subclipse to your Eclipse installation, which lets you use this superior (in my opinion, at least) version-control system directly from Eclipse.
While adding projects to a repository — and managing your project’s resources once it’s there — can be daunting for folks unfamiliar with Subversion, the procedures for common operations are straightforward. This article walked you through the everyday operations to help familiarize you with Subclipse.


Real World Mozilla First XPCOM Component

July 14, 2008

Introduction

Mozilla is a huge platform. In order to work in such a large system and have hundreds or thousands of developers working on the same code, it is necessary to break things into smaller pieces. In Mozilla these pieces are called components, and are shipped as .DLL files (on win32). When one of these .DLL files contains two or more components, it is called a module.

Mozilla developer specialize in one or more components/modules (modules are made up of components). Here is a list of the various modules and their owners and peers: http://www.mozilla.org/owners.html. Module owners are responsible for a module’s code, and have final say about how changes are made to it. The peers are people designated by the module owner to help them maintain the code. Owners and peers do code reviews of patches from the community.

By writing code in components/modules instead of one giant application, there are a number of benefits:

  • Modularization of code, so pieces of a system can be developed, built, replaced independently
  • Increased performance, because you only need to load the components/modules you are using instead of the whole thing
  • Reuse, as separate components become usable in different applications

This raises a question: what are ‘XPCOM components’ components of exactly? The answer is Gecko, Mozilla’s standards compliant, embeddable web browser and toolkit for creating web browsers and other applications. XPCOM is the means of accessing Gecko library functionality and embedding or extending Gecko.

Much has been written about XPCOM elsewhere, and you are encouraged to read more on the theory behind this technology in conjunction with this walkthrough.

Writing FirstXpcom

In this walkthrough we will be creating a simple binary XPCOM component using the build system, called FirstXpcom. While this component won’t do very much yet, it will provide a starting point for many other types of components you may wish to build down the road.

The functionality of this component will become part of a Gecko-enabled application (in this case, a Firefox binary extension). However, it is important to remember that this is only one of many possible uses for it.

NOTE: the following assumes you have used an objdir. Replace all occurrences of $(objdir) with your objdir name.

What is a Component?

Components define certain publicly available functionality. The public portion of a component is defined in an Interface. Interfaces are a kind of contract between implementors (those writing code to implement the interface) and clients (those writing code to use an interface’s implementation). By defining an interface for a component, we advertise to the world what we are willing and able to do. There are other advantages as well:

  • implementors can change their implementation without affecting clients
  • clients can choose between multiple implementations

It should also be noted that components can implement multiple interfaces. This is something we’ll return to later when we discuss interface querying.

Writing FirstXpcom

For this walkthrough we will use the Mozilla build system to create our component. It is also possible to us the Gecko SDK (instructions are here). NOTE: this assumes that you have already done a successful objdir-Firefox build.

Creating Directories

Start by creating a directory for your component, using all lowercase letters:

$ cd mozilla/extensions
$ mkdir firstxpcom

Next, create 2 more directories to hold your component’s public interface (i.e., public) and source code (i.e., src):

$ cd mozilla/extensions/firstxpcom
$ mkdir public
$ mkdir src

Defining an Interface

Now we need to define the component’s public interface. To do this you must create an IDL file (see http://developer.mozilla.org/en/docs/XPIDL). The IDL file defines the component’s public interface in a language neutral way. Mozilla uses the XPIDL format (Cross Platform Interface Definition Language) to define a component’s public interface rather than doing it in C++ header files directly. Using IDL, these interfaces can be defined in a language- and machine-independent way. IDLs make it possible to define interfaces which can then be processed by tools to autogenerate language-dependent interface specifications. The IDL files are used to generate C++ header files.

Create an IDL file for the component in the public directory:

$ cd mozilla/extensions/firstxpcom/public
$ touch IFirstXpcom.idl

Here are the contents of IFirstXpcom.idl

#include "nsISupports.idl"

[scriptable, uuid(...see below...)]
interface IFirstXpcom : nsISupports
{
	attribute AString name;

	long add(in long a, in long b);
};

What does this interface say? To begin, notice the use of nsISupports. This is the fundamental interface that all XPCOM components must implement. What does it do? How is it defined?

nsISupports

nsISupports is the base interface for all XPCOM components (i.e., it is possible to pass any XPCOM component around as an nsISupports object). The methods in nsISupports define basic bookkeeping for an interface’s lifetime (they also defines a way to check at runtime if a component implements a given interface, but more on that later).

Components need to keep track of how many clients hold references to them via an interface. This is known as reference counting on an interface, and it is used to determine when a component can be safely unloaded so that it doesn’t leak (i.e., no one holds a reference any more, but the interface is still in memory).

The members of nsISupports (i.e., QueryInterface, AddRef, and Release) provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used.

One point worth mentioning is that pointers in XPCOM are to interfaces. Interface pointers are known to implement nsISupports, so you can access all of the object lifetime and discovery functionality described above.

So the first line above says, “include the interface for nsISupports (defined in nsISupports.idl) because I’ll need it”, and the fourth line says, “I’m a new interface called IFirstXpcom, but I’m also nsISupports because I inherit from it.”

UUID

[scriptable, uuid(...see below...)]

What does line 3 mean? This says that our component is scriptable, and can be used or implemented in scripting languages, JavaScript for example (see http://developer.mozilla.org/en/docs/Interfaces:About_Scriptable_Interfaces).

Each interface needs to be uniquely identifiable, and Mozilla uses a 128-bit number called a UUID (Universally Unique Identifier) for this purpose. You can generate one in a number of ways:

  • at the command prompt using the command uuidgen:
$ uuidgen
78af1749-014a-47aa-baec-2669670b7601
  • in IRC ask firebot:
/msg firebot uuid

You need to get a UUID and put it in the brackets, for example:

[scriptable, uuid(78af1749-014a-47aa-baec-2669670b7601)]

Attributes and Methods

attribute AString name;

long add(in long a, in long b);

Next comes the body of your interface. Our interface defines one attribute and one method. An attribute is a value you can Get and Set (NOTE: you can specify attributes that are Get only, that is read-only). We have an attribute called name of type AString (a unicode, or two-byte string class. For more details about strings in Mozilla, see the XPCOM String Guide).

Our interface also defines a single method called add, which takes two long integers as input, adds them, and returns the result as a long integer; we’ll write that code below.

Because we are using the Mozilla build system to help us create our component, we can get it to translate our IDL into .h and .cpp stub files automatically. To do this we first have to generate some makefiles.

Build system changes

The first step in making the build system aware of our component is to generate an input file for autoconf to use during the configure step, which will build the necessary Makefile automatically.

mozilla/extensions/firstxpcom/Makefile.in

The Makefile.in should contain the following (NOTE: you can read more about what these files actually mean here):

DEPTH		= ../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE 	= firstxpcom

DIRS		= public \
		  src \
		  $(NULL)

XPI_NAME 	= firstxpcom

# A Unique ID for your extension
INSTALL_EXTENSION_ID	= firstxpcom@senecac.on.ca

# Will create a .xpi in /mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/
XPI_PKGNAME	= firstxpcom

# install.rdf will tell Firefox how to install our extension and 
# this says, "copy install.rdf into our extension dir and xpi"
DIST_FILES = install.rdf

include $(topsrcdir)/config/rules.mk

Note the DIRS variable. It says that the two directories, public and src, will be entered during the build. Because Mozilla’s build system uses recursive make, we also need Makefile.in files in each of these.

mozilla/extensions/firstxpcom/public/Makefile.in

Next we need a Makefile.in the public directory

DEPTH		= ../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE		= firstxpcom
XPIDL_MODULE	= firstxpcom

XPI_NAME 	= firstxpcom

# The files under EXPORTS are copied directly to
# /mozilla/$(MOZ_OBJDIR)/dist/include/firstxpcom
# and are thus accessible from other modules
#EXPORTS       = \
#		myHeader.h \
#		$(NULL)

XPIDLSRCS	= IFirstXpcom.idl

include $(topsrcdir)/config/rules.mk

Here we tell the build system about our component’s name and where its XPIDL file can be found.

mozilla/extensions/firstxpcom/src/Makefile.in

Now a Makefile.in in the src directory:

DEPTH		= ../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

IS_COMPONENT 	= 1
MODULE 	= firstxpcom
LIBRARY_NAME 	= firstxpcom

XPI_NAME 	= firstxpcom

# The REQUIRES section tells make which modules your 
# components uses. This causes the relevant subdirectories
# of /mozilla/$(MOZ_OBJDIR)/dist/include/ to be added to the
# C++ compiler's include path. If you're including Mozilla
# headers and the compiler isn't finding them, it could well
# mean that you haven't listed all of the necessary modules here.
REQUIRES	= xpcom \
		  string \
		  $(NULL)

# The .cpp source files to be compiled
CPPSRCS	= FirstXpcom.cpp \
		  $(NULL)
include $(topsrcdir)/config/rules.mk

EXTRA_DSO_LDOPTS += \
  $(XPCOM_GLUE_LDOPTS) \
  $(NSPR_LIBS) \
  $(NULL)

mozilla/extensions/firstxpcom/install.rdf

The last build-related file we need to write is a file telling Firefox’s addon manager about our extension and how to install it–install.rdf (see http://developer.mozilla.org/en/docs/Install_Manifests for details):

 
 
   
     firstxpcom@senecac.on.ca
     firstxpcom
     0.1
     David Humphrey
     A Simple XPCOM Extension.
     http://zenit.senecac.on.ca/wiki
     
       
         {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 
         2.0
         3.0b2pre 
       
     
   
 

Building FirstXpcom

Now we’re ready to build our component. Add the following line to your .mozconfig file in order to include firstxpcom during the build process:

ac_add_options --enable-extensions=default,firstxpcom

Now you can (BUT DON’T, see below for quick way!!)re-build your tree (also known as “rebuilding world”) by doing:

$ cd mozilla
$ make -f client.mk build

BUT…this takes forever for it to actually reach your code, since it has to traverse the entire tree checking for changes. So, you can bypass the rest of the tree and go directly to your component’s build.

$ cd $(objdir)
$ ../build/autoconf/make-makefile extensions/firstxpcom

Now call make in $(objdir)/extensions/firstxpcom

$ cd $(objdir)/extensions/firstxpcom
$ make

This will create the public and src directories, as well as generate the Makefiles necessary in each. Go into the public directory and call make again, in order to have your .h and .cpp stubs generated (NOTE: this will cause errors, see below):

$ cd $(objdir)/extensions/firstxpcom/public
$ make

If all goes well, you’ll see a hundred lines or so of output from make, ending in an error (NOTE: make will error out, since we have no source files yet). We can safely ignore most of it, but a few lines are significant at this point:

IFirstXpcom.idl
../../../dist/bin/xpidl.exe -m header -w -I../../../../extensions/firstxpcom/public -I../../../dist/idl -o _xpidlgen/IFirstXpcom

Here we can see the build processing our IDL file. As a result of this first (partially) successful run of make, exported and generated header files (i.e.,from our IDL) will be placed in $(objdir)/dist/include/firstxpcom.

Within $(objdir)/dist/include/firstxpcom/IFirstXpcom.h you’ll see the following block of code:

#if 0
/* Use the code below as a template for the implementation class for this interface. */
...
/* End of implementation class template. */
#endif

The code in the middle of this block is what you want to use as the basis for implementing your .cpp file (and .h if you choose to split it out, which we won’t). Copy and paste this implementation stub into the following file: mozilla/extensions/firstxpcom/src/FirstXpcom.cpp

You’ll need to do a search/replace on _MYCLASS_ and change it to the name of your class, in our case, FirstXpcom.

Now you can try and re-run make for your extension:

$ cd $(objdir)/extensions/firstxpcom
$ make

This will produce a host of errors, mostly related to the fact that we don’t have proper includes set-up and it can’t find declarations it needs. You need to add an include for your interface’s generated .h file:

#include "IFirstXpcom.h"

Re-run make.

As an aside, and while you are thinking about Makefiles, you might take a moment to read bug 371201. This is a bug that was identified while the author was trying to debug his Makefile.in files. Ted Mielczarek (ted on IRC) was finally able to spot the problem–a trailing space on XPI_NAME. I share this anecdote as a way to introduce https://bugzilla.mozilla.org, to emphasize the necessity of the community, and to show the kind of problems that one can have writing files for the build system.–UPDATE: this bug has now been fixed by Ted (March 26, 2007).

Examining IFirstXpcom.h

Let’s examine the code more closely and try to make sense of things. Here is the code in $(objdir)/dist/include/firstxpcom/IFirstXpcom.h:

/*
 * DO NOT EDIT.  THIS FILE IS GENERATED FROM c:/temp/proj/ff-trunk/mozilla/obj-fftrunk/extensions/firstxpcom/public/../../../../extensions/firstxpcom/public/IFirstXpcom.idl
 */

#ifndef __gen_IFirstXpcom_h__
#define __gen_IFirstXpcom_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:    IFirstXpcom */
#define IFIRSTXPCOM_IID_STR "78af1749-014a-47aa-baec-2669670b7601"

#define IFIRSTXPCOM_IID \
  {0x78af1749, 0x014a, 0x47aa, \
    { 0xba, 0xec, 0x26, 0x69, 0x67, 0x0b, 0x76, 0x01 }}

class NS_NO_VTABLE IFirstXpcom : public nsISupports {
 public: 

  NS_DECLARE_STATIC_IID_ACCESSOR(IFIRSTXPCOM_IID)

  /* attribute AString name; */
  NS_IMETHOD GetName(nsAString & aName) = 0;
  NS_IMETHOD SetName(const nsAString & aName) = 0;

  /* long add (in long a, in long b); */
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;

};

  NS_DEFINE_STATIC_IID_ACCESSOR(IFirstXpcom, IFIRSTXPCOM_IID)

/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_IFIRSTXPCOM \
  NS_IMETHOD GetName(nsAString & aName); \
  NS_IMETHOD SetName(const nsAString & aName); \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval); 

/* Use this macro to declare functions that forward the behavior of this interface to  another object. */
#define NS_FORWARD_IFIRSTXPCOM(_to) \
  NS_IMETHOD GetName(nsAString & aName) { return _to GetName(aName); } \
  NS_IMETHOD SetName(const nsAString & aName) { return _to SetName(aName); } \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return _to Add(a, b, _retval); } 

/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
#define NS_FORWARD_SAFE_IFIRSTXPCOM(_to) \
  NS_IMETHOD GetName(nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER :  _to->GetName(aName); } \
  NS_IMETHOD SetName(const nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->SetName(aName); } \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return !_to ?  NS_ERROR_NULL_POINTER : _to->Add(a, b, _retval); } 

#if 0
/* Use the code below as a template for the implementation class for this interface. */

/* Header file */
class _MYCLASS_ : public IFirstXpcom
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_IFIRSTXPCOM

  _MYCLASS_();

private:
  ~_MYCLASS_();

protected:
  /* additional members */
};

/* Implementation file */
NS_IMPL_ISUPPORTS1(_MYCLASS_, IFirstXpcom)

_MYCLASS_::_MYCLASS_()
{
  /* member initializers and constructor code */
}

_MYCLASS_::~_MYCLASS_()
{
  /* destructor code */
}

/* attribute AString name; */
NS_IMETHODIMP _MYCLASS_::GetName(nsAString & aName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP _MYCLASS_::SetName(const nsAString & aName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* long add (in long a, in long b); */
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* End of implementation class template. */
#endif

#endif /* __gen_IFirstXpcom_h__ */

What things do you notice? What strikes you?

Use of header guards

Header guards (see Include Guards) are a portable technique for ensuring that includes or defines are only done once, for example, that nsISupports.h only be included once:

#ifndef __gen_nsISupports_h__
#include "nsISupports.h"
#endif

Another way of doing this, especially with the Microsoft compiler, is to use

#pragma once

But this is not portable, and therefore Mozilla doesn’t use it.

Use of macros

Mozilla provides many convenience macros to help C++ developers do common tasks more efficiently. Much of the code involved in making a module/component is generic, and has to be repeated every time. You can spot macros in the code by the fact that they are all capitals, and often begin with NS_*. Examples include:

NS_DECL_ appended with any interface name in all caps will declare all of the methods of that interface for you (nsIFoo –> NS_DECL_NSIFOO). Looking at the code above shows you how this is possible. 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 myClass : public nsISomeClass
{
  public:
    NS_DECL_ISUPPORTS		// declares AddRef, Release, and QueryInterface
    NS_DECL_NSISOMECLASS	// declares all methods of nsISomeClass 

    myClass();
    virtual ~myClass() {}
};

The declaration of nsISomeClass doesn’t include any methods other than the constructor and destructor. Instead, the class uses the NS_DECL_ macro

Also note the use of NS_METHOD and NS_METHODIMP for return type signatures. All XPCOM functions are required to return a result code (nsresult, a integer), which indicates whether or not the function worked (e.g., NS_OK).

Next there is NS_IMPL_ISUPPORTS1. This macro implements the nsISupports interface for you, specifically the implementation of AddRef, Release, and QueryInterface for any object.

NS_IMPL_ISUPPORTS1(classname, interface1)

If your class implements more than one interface, you can simply change the number 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 the following:

NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)

As an example, consider mozilla/xpcom/io/nsBinaryStream.cpp:

NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream)

Use of never before-seen types

A quick scan of the code also reveals types that will be unfamiliar, including: nsAString, nsresult, and PRInt32. What are these?

Because Mozilla is cross-platform almost all of the standard types you are used to have Mozilla-specific versions. For example, PRInt32, which is defined as part of the Netscape Portable Runtime (hence the PR prefix), is a signed 32-bit integer, no matter the OS you are using (see http://developer.mozilla.org/en/docs/PRInt32). Depending on the platform you use this could mean a regular int or a long. The same is true of strings (Mozilla has it’s own string classes — see http://developer.mozilla.org/en/docs/XPCOM_string_guide) because of the need for multi-language support and other things necessary to make Mozilla’s products work around the world.

At first there are so many of these to learn. But you quickly get accustomed to them, and looking at how other people code (via lxr) can help you in this process.

Strange return types

You’ll also notice differences between the original IDL and the autogenerated C++ signatures. In our IDL file, the IFirstXpcom::Add method took two longs and returned a long. However in the C++ code stub it says something different:

 /* XPIDL -- long add (in long a, in long b); */
 NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;

The return value of XPCOM methods generated from XPIDL is always of the type nsresult, and the small macro used in these expansions, NS_IMETHOD, actually represents nsresult. nsresult is returned even when in XPIDL you specify that the method return a void. If your IDL requires a return type, as ours does, that value will be added as a final parameter to the call list–in this case PRInt32 *_retval.

There are other things you should know about making your code compatible on all of the supported Mozilla platforms. You can read about them here: http://www.mozilla.org/hacking/portable-cpp.html.

Module Code

We now need to write a bit of code to get our component registered. Components reside in modules, and those modules are defined in shared library files (i.e., DLLs or DSOs) that typically sit in the components directory of an XPCOM application.

When you build a component or module and compile it into a library, it must export a single method named NSGetModule. This NSGetModule function is the entry point for accessing the library. It gets called during registration and unregistration of the component, and when XPCOM wants to discover what interfaces or classes the module/library implements.

In addition to implementing the module code (i.e., nsIModule), we also have to write code to allow our component to be created at runtime based on an interface rather than concrete types–essentially, abstracting the process of creation so that clients don’t have to know about real classes underneath the interfaces. This means implementing the nsIFactory interface.

Together, these two interfaces will require us to write hundreds lines of code, the majority of which is generic boilerplate code. In order to simplify the work component developers must do, a number of macros help us with this task:

  • NS_GENERIC_FACTORY_CONSTRUCTOR
  • NS_IMPL_NSGETMODULE
#include "nsIGenericFactory.h"
...
// This will result in a function named FirstXpcomConstructor.
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

// 19f3ef5e-759f-49a4-88e3-ed27f9c83011 
#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",
    FIRSTXPCOM_CID,
    "@senecac.on.ca/firstxpcom;1",
    FirstXpcomConstructor
  }
};

NS_IMPL_NSGETMODULE(FirstXpcomModule, components)

First, we have to include nsIGenericFactory.h in order to get NS_GENERIC_FACTORY_CONSTRUCTOR. Now we can add the following line, which will generate a function called FirstXpcomConstructor:

NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

Note: we also could have provided an initialization function to be called after our object gets allocated (i.e., FirstXpcom->Init()):

NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)

Next, we need to create proper identification for our component’s module so that it can be passed to the module implementation macro, NS_IMPL_NSGETMODULE. This macro takes an array of nsModuleComponentInfo so that you can define more than one component per module (remember that a module is a collection of components, and every component belongs to a module so it can get loaded by the system).

Start by generating another uuid, which will be used for identifying our component/class (i.e., we can’t re-use our interface’s uuid), for example:

19f3ef5e-759f-49a4-88e3-ed27f9c83011

Now write a define to make it easier to pass this Class ID around:

#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

Then we can populate our array with a single entry for the FirstXpcom component:

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",			// descriptive name
    FIRSTXPCOM_CID,			// CID from above
    "@senecac.on.ca/firstxpcom;1",	// Contract ID
    FirstXpcomConstructor		// Factory Constructor
  }
};

The last two entries need some explanation. The Component ID is a human-readable string that clients can use to get/create an instance of your class. We’ll see how to do this later on. Here is an example Component ID and what it means:

"@mozilla.org/network/ldap-operation;1"
  • domain = @mozilla.org
  • module = network
  • component = ldap-operation
  • version = 1

The final line, the constructor, is the name of the constructor automatically generated by NS_GENERIC_FACTORY_CONSTRUCTOR. It will be the name of your concrete class followed by “Constructor,” in our case FirstXpcomConstructor.

And that’s it for the module/factory code.

FirstXpcom.cpp

All that remains is our implementation. Here is the final version of FirstXpcom.cpp (emphasis added to highlight changes):

#include  // for printf()
#include "IFirstXpcom.h" // the CPP .h generated from our .idl
#include "nsIGenericFactory.h" // for NS_GENERIC_FACTORY_CONSTRUCTOR()
#include "nsStringAPI.h" // for nsString

class FirstXpcom : public IFirstXpcom
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_IFIRSTXPCOM

  FirstXpcom();

private:
  ~FirstXpcom();

protected:
  nsString mName;
};

NS_IMPL_ISUPPORTS1(FirstXpcom, IFirstXpcom)

FirstXpcom::FirstXpcom()
{
  /* member initializers and constructor code */
  mName.Assign(NS_LITERAL_STRING("FirstXpcom Component"));
}

FirstXpcom::~FirstXpcom()
{
  /* destructor code */
}

/* attribute AString name; */
NS_IMETHODIMP FirstXpcom::GetName(nsAString & aName)
{
  aName.Assign(mName);
  printf("FirstXpcom::GetName\n");
  return NS_OK;
}

NS_IMETHODIMP FirstXpcom::SetName(const nsAString & aName)
{
  mName.Assign(aName);
  printf("FirstXpcom::SetName\n");
  return NS_OK;
}

/* long add (in long a, in long b); */
NS_IMETHODIMP FirstXpcom::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
  printf("FirstXpcom::Add(%d, %d)", a, b);
  *_retval = a + b;
  return NS_OK;
}

// This will result in a function named FirstXpcomConstructor.
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

// 19f3ef5e-759f-49a4-88e3-ed27f9c83011 
#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",
    FIRSTXPCOM_CID,
    "@senecac.on.ca/firstxpcom;1",
    FirstXpcomConstructor
  }
};

NS_IMPL_NSGETMODULE(FirstXpcomModule, components)

Time to call make again:

$ cd $(objdir)/extensions/firstxpcom
$ make

Assuming this works without errors, here’s what has happened:

  • Generated makefiles for your project were created in extensions/firstxpcom/ (remember, we’re under /mozilla/$(MOZ_OBJDIR)/.
  • Exported header files and generated header files (from IDL) in dist/include/firstxpcom/
  • Static libraries for your modules in dist/lib/ (in case other modules want to link statically instead of using XPCOM).
  • XPI file in dist/xpi-stage/firstxpcom.xpi.
  • Everything else in dist/bin/extensions/firstxpcom@senecac.on.ca

Testing FirstXpcom

Checking the Add-on Manager

We’ll write formal tests and code to use our component later. For now, make sure it gets loaded into Firefox and is visible in the Addon Manager. Run Firefox and make sure you can see your extension in the addon manager:

$ cd $(objdir)/dist/bin
$ export MOZ_DEBUG_BREAK=warn
$ firefox.exe -Profilemanager -no-remote

Accessing FirstXpcom from the JavaScript Shell

Now let’s try and access this from JavaScript in the browser. If you haven’t done so already, download and install the Extension Developer’s extension. This will allow you to use the JavaScript Shell inside the browser, making it easy to try out the firstxpcom component.

Launch the JS Shell (Tools > Extension Developer > Javascript Shell) and write some code to access your XPCOM component. You can work interactively without having to define functions, write a complete extension, etc.:

  • Define our component’s ID so we can create an instance of it below.
const cid = "@senecac.on.ca/firstxpcom;1"
print(cid)
  • Now create an instance of our component. The value of obj will be nsISupports at this point (i.e., we can’t call IFirstXpcom’s methods yet).
var obj = Components.classes[cid].createInstance()
  • Next, take the the nsISupports object returned above and query it (i.e., see if it supports your interface type and if so, change to that interface) to IFirstXpcom, often referred to as QI (e.g., “…you need to QI it to IFirstXpcom…”).
obj = obj.QueryInterface(Components.interfaces.IFirstXpcom)
  • At this point we have the ability to use the IFirstXpcom methods and attributes:
var sum
sum = obj.add(4,5)

var name
name = "FirstXpcom!"

obj.name = name
print(obj.name)
alert(obj.name)

When you run this code, also notice how your C++ printf statements are causing messages to stdout in your console window (you may need to scrollback through all the other messages to find them).


XPCOM Component to add two numbers

July 14, 2008

This is a simple tutorial for building XPCOM objects in C++ using Visual Studio. XPCOM is Mozilla’s cross platform component object model, similar to Microsoft’s COM technology. XPCOM components can be implemented in C, C++, and JavaScript, and can be used from C, C++, and JavaScript. That means you can call JavaScript methods from C++ and vice versa.

Development Setup

The simplest way to get an XPCOM component built is to use the Gecko SDK. On Windows, the SDK is built using a Microsoft compiler, so you need to use one too. This tutorial uses Microsoft’s free Visual C++ Express and the sample project in the next paragraph. You could try to use a different vendor’s compiler, but you are going to need to build the SDK from source code. Not the simplest thing to do and it may be incompatible with production releases of Firefox, Thunderbird, and XULRunner from Mozilla.

For example, XULRunner 1.8.0.4 which has a pre-built SDK at gecko-sdk-win32-msvc-1.8.0.4.zip. Extract the SDK to a folder. The tutorial assumes the folder is called xulrunner-1.8.0.4, but you can call yours whatever you want.

You also need a couple of pre-built libraries (glib-1.2.dll & libIDL-0.6.dll) from the wintools.zip archive. Copy glib-1.2.dll & libIDL-0.6.dll into the bin subfolder of gecko-sdk.

Note: wintools.zip seems old and lots of newer MDC documentation refers to moztools.zip archive, but the version of xpidl.exe that comes with the gecko-sdk crashes with the DLL’s from moztools.

Recap:

  • Use the right Gecko SDK for your XULRunner release
  • Use a Microsoft compiler
  • Use pre-built glib-1.2.dll & libIDL-0.6.dll libraries from wintools.zip
  • Download the sample project

Here is what the folder structure looks like:

xpcom-folders.png

Create a VC++ Project

Visual Studio project and file templates (or wizards) for creating XPCOM modules and components do not currently exist. For now, you can use the included XPCOM project that contains a simple XPCOM component. You can use the project as a starting point and modify the component files to add your own functionality.

To make the project, you start with a standard multi-thread DLL project. Then make the following tweaks:

  • Add “..\gecko-sdk\include” to Additional Include Directories
  • Add “..\gecko-sdk\lib” to Additional Library Directories
  • Add “nspr4.lib xpcom.lib xpcomglue_s.lib” to Additional Dependencies
  • Add “XP_WIN;XP_WIN32″ to Preprocessor Definitions
  • Turn off precompiled headers (just to keep it simple)
  • Use a custom build step for the XPCOM IDL file (spawns xpidl-build.bat to process the IDL with Mozilla toolset, not MIDL)

VC++ Express Project: xpcom-test.zip

Note: The project uses xpcom_glue. It also uses frozen linkage (dependent on XPCOM). I am not defining XPCOM_GLUE and I am linking against xpcomglue_s.lib

Create an XPCOM Component

A full tutorial of XPCOM is beyond the scope of this posting. Check out the resources at the end of the tutorial for more information on the world of XPCOM. Ok then, on with the basic, oversimplified example. Your XPCOM component is made up of 3 parts:

  • Component interface described using IDL. The interface defines the methods, including arguments and return types, of the component.
  • Component implementation using C++. The implementation is where the methods actually do the work.
  • Component factory module, also in C++. The factory is in charge of creating instances of the implementations.

Let’s specify a simple interface:

#include "nsISupports.idl"

[scriptable, uuid(263ed1ba-5cc1-11db-9673-00e08161165f)]
interface ISpecialThing : nsISupports
{
  attribute AString name;

  long add(in long a, in long b);
};

Remember to generate your own GUID. The next step is to compile the IDL into a type-library (*.XPT) and a C++ header file (*.H), which we can use to define our implementation object. The blank VC++ project has a BAT file that will create the XPT and the H files. The command executes XPIDL.EXE twice, like this:

{path_to_geckosdk}\bin\xpidl.exe -m header -I..\gecko-sdk\idl {your_idl_file}
{path_to_geckosdk}\bin\xpidl.exe -m typelib -I..\gecko-sdk\idl {your_idl_file}

The generated H file actually has a skeleton implementation (commented out). You can take the code and create implementation H and CPP files. They could look like this:

H file:

#ifndef __SPECIALTHING_IMPL_H__
#define __SPECIALTHING_IMPL_H__

#include "comp.h"
#include "nsStringAPI.h"

#define SPECIALTHING_CONTRACTID "@starkravingfinkle.org/specialthing;1"
#define SPECIALTHING_CLASSNAME "SpecialThing"
#define SPECIALTHING_CID { 0x245626, 0x5cc1, 0x11db, { 0x96, 0x73, 0x0, 0xe0, 0x81, 0x61, 0x16, 0x5f } }

class CSpecialThing : public ISpecialThing
{
public:
	NS_DECL_ISUPPORTS
	NS_DECL_ISPECIALTHING

	CSpecialThing();

private:
	~CSpecialThing();

protected:
	/* additional members */
	nsString mName;
};

#endif

CPP file:

#include "comp-impl.h"

NS_IMPL_ISUPPORTS1(CSpecialThing, ISpecialThing)

CSpecialThing::CSpecialThing()
{
	/* member initializers and constructor code */
	mName.Assign(L"Default Name");
}

CSpecialThing::~CSpecialThing()
{
	/* destructor code */
}

/* attribute AString name; */
NS_IMETHODIMP CSpecialThing::GetName(nsAString & aName)
{
	aName.Assign(mName);
	return NS_OK;
}
NS_IMETHODIMP CSpecialThing::SetName(const nsAString & aName)
{
	mName.Assign(aName);
	return NS_OK;
}

/* long add (in long a, in long b); */
NS_IMETHODIMP CSpecialThing::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
	*_retval = a + b;
	return NS_OK;
}

Lastly, we need to create the module implementation. I put this together from some samples I found on the MDC site:

#include "nsIGenericFactory.h"
#include "comp-impl.h"

NS_GENERIC_FACTORY_CONSTRUCTOR(CSpecialThing)

static nsModuleComponentInfo components[] =
{
    {
       SPECIALTHING_CLASSNAME,
       SPECIALTHING_CID,
       SPECIALTHING_CONTRACTID,
       CSpecialThingConstructor,
    }
};

NS_IMPL_NSGETMODULE("SpecialThingsModule", components)

Assuming you have the right SDK and setup the include and LIB folders correctly, the project should build your XPCOM component.

Test Component in a XULRunner Application

In order to test your component in a XULRunner application, you need to “install” the component, “clear” the component registry, and use the component from JavaScript.

  1. Install Component: Copy your XPT and DLL files to the {app}/components folder. You should not put your component in the xulrunner/components folder.
  2. Clear Registry: Increment the BuildID in your {app}/application.ini.
  3. Use in JavaScript:
function doXPCOM() {
  try {
    const cid = "@starkravingfinkle.org/specialthing;1";
    var obj = Components.classes[cid].createInstance();
    obj = obj.QueryInterface(Components.interfaces.ISpecialThing);
  }
  catch (err) {
    alert(err);
    return;
  }

  var res = obj.add(3, 4);
  alert('3+4 = ' + res);

  var name = obj.name;
  alert('Name = ' + name);

  obj.name = 'New Name';
  name = obj.name;
  alert('Name = ' + name);
}


More on XPCOM

July 14, 2008

Before getting into the nuts and bolts of constructing your own XPCOM-enabled application or even your own XPCOM components, you will need a proper development environment. The best way to verify that all of the tools are in place and working properly is to do a build of the Mozilla browser.

Build the Mozilla

Mozilla is built on top of XPCOM and XPConnect so building it gives you both libraries and a suite of useful XPCOM components – some of which do not depend upon cohabitation with a browser. Go to http://www.mozilla.org, download the source code and supplemental development tools, and build everything. The Mozilla code uses generic make files, shell scripts, and Perl scripts in its build process instead of compiler-specific project files. This “least common denominator” approach is the only way to make sure that the same code gets built the same way on different platforms.

Be aware that this approach eats up lots of disk space. As of this writing, just unpacking the source code tarball held 160,975,003 bytes in over 28567 files. Due to file system inefficiencies on NTFS, this works out to 244,293,632 bytes of actual disk space used. That’s only the source code! Source code and binaries after a release build add up to just under 450 megabytes. This doesn’t include disk space for additional tools or space for a debug build.

CVS

Part of the magic of getting the same source code to compile on different platforms comes from the availability of some tools that have themselves been ported to numerous platforms. Some of those tools are gmake, CVS, and infozip.

CVS — the Concurrent Versioning System — is a simple but well-used and well-tested multiuser source control and versioning system for allowing large numbers of programmers to collaborate on large projects. It is comparable in function to Microsoft’s SourceSafe tool. RCS is another similar UNIX-oriented program.

CVS is a crucial weapon in the Mozilla arsenal for tracking bugs and patches. Your main interest in CVS is the ability to incrementally download the latest changes to the source code just as soon as they become available to everyone else. You can skip CVS if you don’t mind waiting for the next tarball release.

The CVS server requires a password and, unless you’ve gained the confidence of a module owner to nominate you for write access privileges, you will be using the generic, read-only account. This does not mean you aren’t allowed to contribute — far from it. It just means you will need to submit your patches directly to one of the module owners for consideration.

Setting up a Windows environment

Here’s the fast track to getting through this step on MS Windows:

  1. Make sure Microsoft Visual C++ 6 is installed along with all the latest updates (they were up to Service Pack 4 last I checked). There are efforts underway to support other compilers on Windows such as gcc, but for now only VC6 is known to work. VC5 with SP3 is also supposed to work, but I haven’t tried it.
  2. Make sure your compiler, linker, and make tool can be run from a shell window.
    • type “cl” – you should see something like “Microsoft (R) 32 bit Optimizing C/C++ compiler version ..”
    • type “nmake” – you should see something like “Microsoft (R) Program Maintenance Utility …”
    • type “lib” – you should see something like “Microsoft (R) Library Manager …”

If none of these work, see the environment variables hint under step 5 below.

  1. Download the source tarball and unzip the contents to a “mozilla” directory on a disk with lots of space (about 1 gig minimum or 2 gigs if you plan to do debug and release builds at the same time).
  2. Download the supplemental tools needed like cygwin, Active State Perl and infozip ( Note that you can use winzip or pkzip to unzip the tarball, but the Mozilla makefiles use infozip as part of the build.
  3. The following environment variables need to be defined:

For Windows 9X add a few commands to AUTOEXEC.BAT like so:

 set PATH=%PATH%;C:\cygwin\bin;C:\progra~1\micros~1\common\msdev98\bin;c:\perl\bin
 C:\progra~1\micros~1\vc98\bin\vcvars32
 set MOZ_BITS=32
 set MOZ_src=C:
 set MOZ_TOOLS=C:
 set CVSROOT=:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot

For Windows 2000 navigate to the Control Panel (click Start/Settings/Control Panel), click on “System”, select the “Advanced” tab and click on “Environment Variables” to get the edit dialog. Check for the following or something equivalent for your system (see Table 1).

Table 1. System environment variables [

PATH

C:\Program Files\Microsoft Visual Studio\Common\Tools\WinNT;
C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin;
C:\Program Files\Microsoft Visual Studio\Common\Tools;
C:\Program Files\Microsoft Visual Studio\VC98\bin;
C:\Perl\bin;
C:\cygwin\bin

include

C:\Program Files\Microsoft Visual Studio\VC98\atl\include;
C:\Program Files\Microsoft Visual Studio\VC98\mfc\include;
C:\Program Files\Microsoft Visual Studio\VC98\include

lib

C:\Program Files\Microsoft Visual Studio\VC98\mfc\lib;
C:\Program Files\Microsoft Visual Studio\VC98\lib

MSDevDir

C:\Program Files\Microsoft Visual Studio\Common\MSDev98

MOZ_BITS

32

MOZ_SRC

C:

MOZ_TOOLS

C:

CVSROOT

:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot

The exact PATH specification will vary depending upon where you’ve installed some of your tools. The MOZ_SRC variable should point to the directory just above the mozilla directory tree. The MOZ_TOOLS variable should point to the directory just above your cygwin tools bin directory.

Cygwin GNU tools

The first set of third-party tools to install are a set of GNU command-line tools ported for Windows. Download the file “setup.exe” and run it (you need an active Internet connection for this to work). The setup program downloads the selected components and installs them. The components needed are ash, cygwin, diff, fileutils, gawk, grep, sed, shellutils, textutils, unzip, and zip. You could also pick up Perl and CVS while you are downloading these GNU tools but don’t do it — the build experts are recommending different versions, which are available elsewhere. Downloading the different modules can take time so some patience is required.

Infozip

The build process requires infozip to make JAR files. The cygwin setup above should have given you the option to download and install zip and unzip (the two pieces of infozip). As an alternate route for getting the infozip, tools you can follow these steps. The zip tool distribution comes in a ZIP file, so to unzip it you will need the separate unzip as well. Download the files “unz542xN.exe” and “zip23xN.zip” from the infozip site (or one of its mirrors) to a temporary directory and type these commands:

cd \temp
unz542xN
unzip zip23.zip
copy *.exe c:\bin

Netscape Wintools

Netscape has modified a handful of the GNU command-line tools to solve some problems mostly for makefile compatibility with the GNU-styled UNIX builds. You should have already added the environment variables mentioned earlier, as the installer for this bundle uses MOZ_TOOLS variable to determine where to install a few of the executables. The following, when done from the command prompt, will unzip the wintools.zip file to the default directory “buildtools” and complete the installation:

cd \temp
unzip wintools.zip
cd \buildtools\windows
install

The above step actually creates the “C:\bin” and “C:\include” directories (assuming you’ve defined MOZ_TOOLS to be “C:”). You can delete everything under “buildtools” after this step if you want to save some space. Unlike the other tools, the path to Netscape Wintools should not be included in the PATH environment variable. The makefiles use MOZ_TOOLS to find gmake and other Netscape-modified binaries.

Perl

Installing Active-State Perl is dreadfully easy — you can download it as a Windows installer module or MSI file. Using Windows Explorer, browse to the folder where you downloaded it, and click on the file named something like “ActivePerl-5_6_0_616-MSWin32-x86-multi-thread.msi”.

There are two minor potential “gotchas” to be aware of. First, the installer will add “\Perl\bin” to your PATH environment variable. If you are running Windows NT or Windows 2000, this will only apply to the currently logged-in user. Second, if you are running Windows 9X or NT, then you may need to download the Microsoft Windows Installer package from Microsoft’s Web site.

As a final test, reboot your machine to make sure any changes took effect. Open up a command-line window and try to run each of the third party tools by typing their names at the command prompt. Some commands to try are ash, diff, grep, perl, and unzip.

The preceding steps are only necessary when you are creating or recreating your build environment. These next steps are the ones you will need to perform each time you wish to actually build Mozilla.

If you plan to do a debug build, enter this command just before the build:

set MOZ_DEBUG=1

A few extra neat-to-know options are:

  • MOZ_SVG which adds SVG support
  • MOZ_MATHML which adds MathML support
  • MOZ_LDAP_XPCOM for LDAP support
  • MOZ_DISABLE_JAR_PACKAGING is useful if you do not want to mess with using zip in your builds
  • Some others worth playing with are MOZ_LITE, MOZ_MEDIUM, and MOZ_MAIL_NEWS

Finally, run the following command to kick off the build:

nmake /f client.mak build_all

Most of the common potential errors at this point involve the makefile issuing a command that fails because either the tool involved in the command is missing or is not visible from the search path. Once the kinks in the build environment are ironed out, the build process will run for a couple of hours (I am not kidding).

Even with a correct build environment, you may run into problems. To validate the above procedure, I tested it against the mozilla-0.7 tarball. I quickly ran into problems with gmake crashing on the NSPR module when the PR_CLIENT_BUILD_WINDOWS option was set (NSPR is the very first module to get built). I downloaded and unzipped the mozilla-0.8 tarball, re-ran the above command and everything compiled fine. The Mozilla build page offers a list of common errors and how to resolve them.

CVS on Windows

Download CVS binaries for Windows from the cvshome.org site.

Here’s the command sequence to pull the whole source tree from the CVS server:

  • set CVSROOT=:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
  • set HOME=\TEMP
  • cvs login (answer the prompts for the CVS login)
  • cvs checkout mozilla/client.mak
  • cd mozilla
  • nmake -f client.mak pull_all

Here’s the command sequence to update an existing source tree (like the latest tarball) by having the CVS server only deliver those files that have changed:

  • set CVSROOT=:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
  • set HOME=\TEMP
  • cvs login (answer the prompts for the CVS login)
  • cd mozilla
  • cvs -z3 checkout -PA mozilla/client.mk
  • nmake -f mozilla/client.mak checkout MOZ_CO_FLAGS=-PA

Setting up a Linux environment

In contrast to Windows, most of the programmer-friendly Linux distributions include all the tools needed to build Mozilla. If you’ve got a Linux CD like Debian or RedHat or Slackware, chances are that all the pieces you need are already there. The only issue for getting your build environment up and running is verifying which versions are installed.

Here’s a summary of the packages needed:

  • C++ Compiler
    • egcs
    • gcc
  • GNU make
  • GTK/GLib
  • Perl 5
  • zip

Set up your environment variables like so:

  • setenv MOZ_BITS 32
  • setenv MOZ_src=/usr/home
  • setenv CVSROOT=:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot

Run the config tool from the mozilla directory like so:

cd mozilla
./configure

From the mozilla directory, do a default build:

gmake

From the mozilla directory, do a manual build:

gmake -f client.mk build

CVS on Linux

Use these commands to pull the whole source tree from the CVS server:

  • setenv CVSROOT :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
  • cvs login (answer the CVS server login prompts)
  • cvs checkout mozilla/client.mk
  • cd mozilla
  • make -f client.mk checkout

Use these commands to update an existing source tree (like the latest tarball) with the latest sources from the CVS server:

  • setenv CVSROOT :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
  • cvs login (answer the CVS server login prompts)
  • cd mozilla
  • cvs -z3 checkout -PA mozilla/client.mk
  • make -f mozilla/client.mk checkout MOZ_CO_FLAGS=-PA

Other environments

Check the build page for explicit build instructions for Win32, Mac, UNIX, Linux, BSD, and other platforms. If you have actually followed through all of this and successfully established a working build environment on your computer, take a bow (and a break)! Pat yourself on the back, for now you can actually start to do something productive.

Enabling XPCOM in an application

Before an application can actually begin using XPCOM components, there are a set of libraries that must be loaded and initialized for the XPCOM framework to operate. Here’s a sample app to do that:

Listing 1. Sample app to load and initialize necessary libraries

 
#include <stdio.h>
#include <nsIServiceManager.h>
#include <nsISomething.h>

int main()
{
   static const char szContractId[] =
      "Your component's contract ID goes here";
   nsresult rv;

   // Initialize XPCOM and check for failure ...
   rv = NS_InitXPCOM(nsnull, nsnull);
   if ( NS_FAILED(rv) )
   {
      printf("Calling NS_InitXPCOM returns [%x].\n", rv);
      return -1;
   }

   // optional autoregistration - forces component manager to check for new components.
   (void)nsComponentManager::AutoRegister(nsIComponentManager::NS_Startup, nsnull);

   // Create an instance of our component
   nsCOMPtr
 mysample = do_CreateInstance(szContractId, &rv);
   if ( NS_FAILED(rv) )
   {
      printf("Creating component instance of %s fails.\n", szContractId);
      return -2;
   }

   // Do something useful with your component ...

   // (main body of code goes here)

   // Released any interfaces.

   // Shutdown XPCOM
   NS_ShutdownXPCOM(nsnull);
   return 0;
}

The two crucial calls are NS_InitXPCOM and NS_ShutdownXPCOM. The XPCOM core libraries are normally located in the same directory as the application, and an additional subdirectory named “components” is required. Once XPCOM has been initialized, the two big XPCOM components of immediate interest for doing something productive are the Component Manager and the Service Manager. The call to AutoRegister is actually optional, and is really only needed when a new component is installed.

The above example assumes a stand-alone application that does not need browser support. nsISomething is a placebo for some actual interface. Real-world application code that makes use of the interface would be placed where the “main body of code goes here” comment appears above.

Component manager

The component manager does just what its name implies. It keeps track of what components are currently installed and what DLL or shared library must be loaded to create a specific component. The components subdirectory mentioned above is where the component manager expects to find any components. It scans this directory in the AutoRegister step above looking for components not already registered and adds a descriptive entry into a private map file. Subsequent requests for components happen much quicker because it already knows from the map which DLL or shared library to load. Components are identified in one of two ways: a 128-bit UUID known as a class ID or CID or a short text name known as a Contract ID.

Note to MSCOM programmers: a contract ID is functionally equivalent to an MSCOM Program ID or ProgID.

Here are some core methods offered by the component manager in IDL. The first one looks up the class ID for a given contract ID. If you plan to create a lot of components of the same class and you only know the component’s contract ID, you can improve performance by calling this method first and using the shorter, faster class ID for subsequent calls to createInstance.

void contractIDToClassID(in string aContractID, out nsCID aClass);

This next method just does the inverse of the above:

string CLSIDToContractID(in nsCIDRef aClass, out string aClassName);

The following method verifies that some component has been registered and is therefore available for use:

boolean isRegistered(in nsCIDRef aClass);

The next two methods do all the grunt work for loading an arbitrary XPCOM component. You get your choice of identifying a component in the first parameter by class ID with createInstance or contractID in createInstanceByContractID. The second parameter aDelegate is only needed when you are doing what is called aggregation and is usually set to nsnull. The third parameter is the interface IID.

voidPtr createInstance(in nsCIDRef aClass, in nsISupports aDelegate, in nsIIDRef aIID);
voidPtr createInstanceByContractID(in string aContractID, in nsISupports aDelegate, in nsIIDRef IID);

The IDL source for all of the component manager methods can be found in nsIComponentManager.idl.

Service manager

XPCOM services are referred to in some books as singleton objects. No matter how many times you request a service you will always receive an interface to the same component. You’ve already seen the biggest service of them all — the component manager. How’s this for indirection: the service manager is itself a service. In a nutshell, the service manager takes care of loading and unloading services. When a request is made for a service that is already loaded, the service manager is smart enough to return another pointer to the existing service instead of trying to construct another object. Note how this behavior differs from the component manager which gives you a fresh new component on each request. Services are usually requested using the NS_WITH_SERVICE macro as illustrated in Listing 2.

Listing 2. Service request

 
{  // enter scope of service smart pointer ...
   NS_WITH_SERVICE(nsIMyService, service, kMyServiceCID, &rv);
   if (NS_FAILED(rv)) return rv;
   service->DoSomething(...);    // use my service
} // leaving scope of service smart pointer ...

Note that the NS_WITH_SERVICE macro uses nsCOMPtr to create a smart pointer. Some examples of services are listed in Table 2.

Table 2. Examples of services

  • LDAP
  • WebShell
  • JSRuntime
  • Editor
  • EventQueue
  • RDF
  • SMTP
  • IMAP
  • POP3
  • NNTP
  • DNS
  • Error
  • Logging

Category manager

The component manager and service manager will fetch a component given its contract or class ID. How would you go about finding components without either of these? The answer to this question is the category manager. The category manager provides a directory of class IDs grouped into categories. When I say “directory” here, think phone book (as in the Yellow Pages) and not disk drive. Using the phone book analogy, if we wanted to find all of the hotels in the area, we could look up “hotels” in the phone book.

Suppose you’ve written a neat editor with lots of word processing features that supports multiple document types through a generic set of interfaces. The document types you chose to support are text, HTML, RTF and PDF — but you’ve also registered your document handlers under the “document handler” category and written your program to always check the document handler category to determine what document types are available.

Your friend decides your program desperately needs to support WordPerfect files so she creates a new document handler component that implements the same set of interfaces, and she registers it under the document handler category. Any user of your program can now download, install and begin using your friend’s new document handler without any extra work on your part.

Components grouped under a category usually have something in common, like a predefined set of interfaces. The category becomes an implied contract for any component registering itself under that category. Categories are a very powerful means of achieving object independence since the code that makes use of a category only cares about the interfaces and can divorce itself from any specific implementation. Despite this power, categories are one of the most under-used features of XPCOM.


XPCOM component basics

July 14, 2008

The idea behind XPCOM as a technology is to provide a modular framework that is platform- and language-neutral. There is very little that a component written in C++ can do that can’t be done in JavaScript or some other scripting language. The mechanism used in XPCOM to accomplish this feat is a type library.

Type library

A type library provides a common data format or interchange mechanism for describing the methods, attributes, parameters, and interfaces of a component. By establishing a common format, the same interfaces can be described across multiple platforms and multiple programming languages. This is useful for supporting a generic marshalling or proxy mechanism. Using the type info, a program can determine every parameter of any given method or attribute on some interface. With this knowledge, it can move data back and forth between the interface and some other environment. That other environment can be a scripting engine or a proxy mechanism for crossing thread, process, or network boundaries. In the case of a scripting engine, this is how a component gets defined in the scripting environment so that scripted code can invoke methods on a component’s interface.
XPConnect is an additional layer built on top of XPCOM that can marshal an XPCOM interface into the JavaScript engine by reading an XPCOM type library file. XPConnect also allows XPCOM components to be written entirely in JavaScript so you can have C++ code call a JS component, or use JS to load and manipulate a compiled C++ component. In addition to JavaScript, the Python language has been added as another scripting alternative using a mechanism similar to XPConnect.

Interface description

The language-neutral way to specify an interface is to use an IDL or interface description language. The tool to create a type library file from an interface description is an IDL compiler. The IDL dialect used in XPCOM is slightly different from those used in OMG CORBA or Microsoft IDL, so a different IDL compiler — the xpidl compiler –– is used. An interesting feature of the xpidl compiler is the option to generate C++ code stubs from an interface definition. This feature has the effect of writing nearly all of the declaratory C++ code when starting a new project. It’s like a coding wizard to help you get started. CORBA and Microsoft IDL compilers offer similar features. Here is a synopsis of running xpidl from a shell prompt.
Listing 1. xpidl from a shell prompt

Usage: xpidl [-m mode] [-w] [-v] [-I path] [-o basename] filename.idl
-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)

While generating C++ code is a bonus, the real purpose of an IDL compiler is to produce a type library file for each module. C++ code can take advantage of the IDL-generated C++ header files to describe interfaces as virtual methods on a C++ class. Since the interface description (in the form of a C++ header file) is used at compile time, it is referred to as early binding. The type library file offers the same functionality for instances where some piece of code wishes to use a component never seen before, hence no header files are available. It can learn the interfaces after the fact. This is referred to as late binding.
The XPIDL syntax for specifying an interface is; the interface keyword followed by the interface name, a colon, the name of a base interface (usually nsISupports), an open curly brace, a list of attributes and methods that each end in a semicolon, and a closing curly brace and semicolon. Attributes are declared using the attribute keyword. The parameters to methods can be declared as input or output parameters by prefixing them with the in or out keywords. Listing 2 shows a sample interface used to describe the computer’s screen.
Listing 2. Sample interface

#include “nsISupports.idl”
[scriptable, uuid(f728830e-1dd1-11b2-9598-fb9f414f2465)]

interface nsIScreen  : nsISupports
{
void GetRect(out long left, out long top, out long width, out long height);
void GetAvailRect(out long left, out long top, out long width, out long height);
readonly attribute long pixelDepth;
readonly attribute long colorDepth;
};

Examining the above interface description we can see that the interface is named nsIScreen, and that it has two methods (GetRect and GetAvailRect), and two attributes (pixelDepth and colorDepth). Just ahead of the interface keyword is a clause bound inside a pair of square brackets. This clause is an optional part of the interface description and supplies some useful metadata. The scriptable keyword tags the interface as a candidate for marshalling in JavaScript and other scripting languages. The uuid keyword specifies the interface’s UUID or interface ID. nsIScreen’s base interface is nsISupports (appears just after the colon) which means that whatever methods and attributes described in nsISupports are also found in nsIScreen (more on what these are later). Attributes are distinguished from methods by the attribute keyword. In this case, both attributes can be examined but not set; the clue being the readonly keyword. (See the Resources section for a link to a detailed description of xpidl syntax.)

Interface discovery

XPCOM uses an interface-based approach to handling components. Client code is forced to interact with a component strictly through the interfaces provided by that component. Most components support two or more interfaces so the interface dispensing mechanism (that’s the QueryInterface method — more on this in a moment) has to provide some facilities for managing interfaces, particularly:
·    Determining what interfaces are supported by a component
·    Switching from one interface to another (and back again)
I’ll group these two items together and call them interface discovery. A core requirement for an XPCOM component is that it support a standard interface to handle interface discovery, and that this standard interface must be the base interface from which any other XPCOM interface extends to provide additional methods and functionality. That standard interface is named nsISupports and appears in simplified IDL in Listing 3.
Listing 3. Standard interface, nslSupports

interface nsISupports
{
void QueryInterface(in nsIIDRef uuid, out nsQIResult result);
nsrefcnt AddRef();
nsrefcnt Release();
};

The first method, QueryInterface, is the one that actually takes care of interface discovery. The other two methods, AddRef and Release, provide for lifetime management of a component (how long a component should exist) through reference counting.
The first parameter to QueryInterface is a reference to a UUID or universally unique ID number that is 128 bits long (16 bytes). As an example, here is the interface ID for nsISupports: 00000000-0000-0000-c000-000000000046.
UUIDs are commonly written using hexadecimal digits in a hyphenated form. This ID number specifies an interface that may or may not be supported by the component being queried. The component may either return an error result code or set the second parameter to the address of the requested interface and return a success result code. XPCOM software designers are expected to exercise care when creating new interfaces to make sure that any new interfaces are assigned unique interface IDs.
Listing 4 contains a JavaScript sample that uses QueryInterface to switch among different interfaces on the same instance of some component.
Listing 4. Switching interfaces

// first, we create an instance of something…
var file = components.classes[“@mozilla.org/file/local;1”].createInstance();
// second, we specify which interface we actually want to use.
file = file.QueryInterface(Components.interfaces.nsIFile);
// do something generic with the nsIFile interface here.
file.create(NORMAL_FILE_TYPE, 0377);
var size = file.fileSize;
// later on, we check to see if an extended interface is supported.
var local = file.QueryInterface(Components.interfaces.nsILocalFile);
if (local)
{
// do something specific to the nsILocalFile interface…
local.initWithPath(‘/usr/tmp/scratch.txt’);

// suppose we’re now in some scope where the file variable is no longer
// visible to use but we want to call some function that absolutely
// insists on only accepting an nsIFile and not an nsILocalFile.
// no problem, just QI over to the other interface like so …

var insists = local.QueryInterface(Components.interfaces.nsIFile);
if (insists)
{
// at this point we can call our hypothetical function
// to do some generic file processing…
hypothetical(insists);
}
}

nsISupports

Component creation

Did you notice that funny looking string in the JavaScript example above – the one that reads “@mozilla.org/file/local;1”? That’s called a contract ID. Explicitly creating a component requires one of two forms of identification as a means of specifying to the component manager which component to create. One form is the component’s class ID which is just a 128 bit number. The other form is a contract ID that is really just a text string. Either is sufficient for requesting a component from the component manager. The intent of a contract ID is to promise a set of behavior and related interfaces to clients wishing to use the component. The recommended format of a contract ID is a one-line string as follows <!– /* Font Definitions */ @font-face {font-family:Wingdings; panose-1:5 0 0 0 0 0 0 0 0 0; mso-font-charset:2; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:0 268435456 0 0 -2147483648 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:””; margin:0in; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:”Times New Roman”; mso-fareast-font-family:”Times New Roman”;} p {mso-margin-top-alt:auto; margin-right:0in; mso-margin-bottom-alt:auto; margin-left:0in; mso-pagination:widow-orphan; font-size:12.0pt; font-family:”Times New Roman”; mso-fareast-font-family:”Times New Roman”;} pre {margin:0in; margin-bottom:.0001pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:10.0pt; font-family:”Courier New”; mso-fareast-font-family:”Times New Roman”;} @page Section1 {size:8.5in 11.0in; margin:1.0in 1.25in 1.0in 1.25in; mso-header-margin:.5in; mso-footer-margin:.5in; mso-paper-source:0;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:824278441; mso-list-template-ids:429948326;} @list l0:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:.5in; mso-level-number-position:left; text-indent:-.25in; mso-ansi-font-size:10.0pt; font-family:Symbol;} ol {margin-bottom:0in;} ul {margin-bottom:0in;} –>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:”Table Normal”;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:””;
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin:0in;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:”Times New Roman”;
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}

@<internetdomain>/module[/submodule[...]];<version>[?<name>=<value>[&<name>=<value>[...]]]

The square brackets above [like this] imply something optional. Here are some examples:

    @mozilla.org/file/directory_service;
      @mozilla.org/file/local;1
        @mozilla.org/file;1
          @mozilla.org/filelocator;1
            @mozilla.org/filepicker;1
              @mozilla.org/filespec;1

              Each example includes a version number of one. A subsequent contract ID with a version of two does not necessarily imply backward compatibility. A contract ID with a different version number may include other contracts’ promised behaviors and interfaces as part of its own promised behaviors and interfaces.

              Lifetime management

              Components must keep count of how many outstanding interfaces have been issued. It would be a very bad thing for a component to be destroyed while some other piece of code is attempting to make use of one of its interfaces. When an object dispenses another copy of an interface, it increments its internal reference count. When an object’s interface is released, its reference count decrements. When an object’s reference count drops to zero, it destroys itself. That’s reference counting in a nutshell.
              Generally, the QueryInterface method performs an implicit AddRef on the component being queried when returning a valid interface pointer. When a piece of client code is done using the interface, it calls the Release method to indicate to the component that it is done with that interface. This is an important burden on all XPCOM client software: for every QueryInterface or AddRef on a component there must also be a Release. A large portion of XPCOM bugs can be traced to either a missing or an extra Release on a component.

              Macros and smart pointers

              To combat this type of error, XPCOM includes some C++ templates that allow you to declare a smart interface pointer. The templates give you a “set and forget” pointer. Set the pointer to an interface and it will remember to release the interface for you. Set the pointer to another interface and it will release the previous one. Sounds simple enough. You declare your smart pointer within the scope needed and assign it to an interface. You use the smart pointer as you would a plain vanilla interface pointer. When the smart pointer loses scope, its built-in destructor will call the Release method for you.
              Taking a look at just about any Mozilla code that uses nsCOMPtr or nsIPtr you will see something like the code in Listing 5.
              Listing 5. Mozilla code using nsCOMPtr or nslPtr

              nsresult nsExample::DoSomething(void)
              {
              nsresult rv;
              nsCOMPtr<nsIManager> pManager;
              *aResult = nsnull;
              pManager = do_GetService(“Some contract ID goes here”);
              if (pManager == nsnull)
              return NS_ERROR_NOT_AVAILABLE;
              rv = pManager->ManageSomething(); // do some more work here …
              return rv;
              }

              In Listing 5, a smart pointer to the fictional nsIManager interface is declared with the name pManager (see the line that starts with “nsCOMPtr ..”). The smart pointer is assigned to some service. After testing that a valid pointer was indeed returned, the code above dereferences the pointer to call the ManageSomething() method. When the above function returns, the pManager smart pointer will be destroyed — but not before calling Release on the interface pointer held inside.
              XPCOM expedites a lot of the declaratory grunt work demanded by C++ through the use of a family of C macros. Most interfaces return a nsresult. In most cases, the magic value to check for in an nsresult is NS_OK. (For an exhaustive list of nsresult values take a look at nsError.h.)
              The XPCOM include files nsCom.h, nsDebug.h, nsError.h, nsIServiceManager.h and nsISupportsUtils.h provide some additional macros for testing, debugging and implementation.
              When you browse the header files of various C++ XPCOM components you’ll see NS_DECL_ISUPPORTS as part of the class definition. This macro provides the definitions for the nsISupports interface.
              Listing 6. Definitions for nsISupports

              public:
              NS_IMETHOD QueryInterface(REFNSIID aIID void** aInstancePtr);
              NS_IMETHOD_(nsrefcnt) AddRef(void);
              NS_IMETHOD_(nsrefcnt) Release(void);
              nsrefcnt mRefCnt;

              When you browse a component’s corresponding implementation file, you’ll see another mysterious one-line macro named NS_IMPL_ISUPPORTS1 (or similar). This macro provides the actual implementation of the nsISupports interface. The digit “1” at the end of the macro denotes the number of interfaces (besides nsISupports) that the component implements. If a class implemented two interfaces it could use NS_IMPL_ISUPPORTS2.
              Remember the mRefCnt data member above — we’ll be making reference to it again shortly.
              Here’s how the NS_IMPL_ISUPPORTS1 macro is defined in nsISupportsUtils.h:
              Listing 7. NS_IMPL_ISUPPORTS1

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

              As you can see, it’s just defined in terms of three other macros. Digging further, we start with the definition for NS_IMPL_ADDREF:
              Listing 8. NS_IMPL_ADDREF

              #define NS_IMPL_ADDREF(_class)
              NS_IMETHODIMP_(nsrefcnt) _class::AddRef(void)
              {
              NS_PRECONDITION(PRInt32(mRefCnt) >= 0, “illegal refcnt”);
              NS_ASSERT_OWNINGTHREAD(_class);
              ++mRefCnt;
              NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this));
              return mRefCnt;
              }

              Finally some real code to look at! Out of five lines of code, three of them are debugging macros that we can safely ignore. The last line of code is a return statement. Any code calling AddRef is supposed to discard the value returned, so we can ignore the return statement.
              The one line of code of interest to us is the ++mRefCnt statement. All it does is increment a counter so every time we call AddRef on some interface, all we are doing (in all likelihood) is causing that component to increment some internal counter. Next, let’s peek at the NS_IMPL_RELEASE macro:
              Listing 9. NS_IMPL_RELEASE macro

              #define NS_IMPL_RELEASE(_class)
              NS_IMETHODIMP_(nsrefcnt) _class::Release(void)
              {
              NS_PRECONDITION(0 != mRefCnt, “dup release”);
              NS_ASSERT_OWNINGTHREAD(_class);
              –mRefCnt;
              NS_LOG_RELEASE(this, mRefCnt, #_class);
              if (mRefCnt == 0) {
              mRefCnt = 1; /* stabilize */
              NS_DELETEXPCOM(this);
              return 0;
              }
              return mRefCnt;
              }

              Again, we’ve got three statements involving debugging macros that we can safely ignore, along with two return statements that we can ignore for reasons explained above.
              The two statements we care about are –mRefCnt, which decrements the object’s counter and if (mRefCnt == 0), which tests to see if the counter has reached a value of zero. The next couple of lines tell us that the object will delete itself when this internal counter reaches zero.
              In summary, AddRef increments the counter, Release decrements the counter — and when the number of calls to AddRef equal the number of calls to Release, the net reference count becomes zero and the component destroys itself. This whole reference-counting idea is starting to look fairly straightforward. Next we’ve got NS_IMPL_QUERY_INTERFACE1 defined in Listing 10.
              Listing 10. NS_IMPL_QUERY_INTERFACE1

              #define NS_IMPL_QUERY_INTERFACE1(_class, _i1)
              NS_INTERFACE_MAP_BEGIN(_class)
              NS_INTERFACE_MAP_ENTRY(_i1)
              NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, _i1)
              NS_INTERFACE_MAP_END

              Drat! More macros to look up. By now, all of the MFC coders are chuckling because they’ve seen this kind of macro indirection nonsense before. These macros are building an interface map for the component so their order and position is important to creating a QueryInterface implementation. Undaunted by this indirection, we plow ahead and look at NS_INTERFACE_MAP_BEGIN. It turns out that it’s just an alias for NS_IMPL_QUERY_HEAD which expands into the code in Listing 11.
              Listing 11. NS_INTERFACE_MAP_BEGIN

              #define NS_IMPL_QUERY_HEAD(_class)                                       \
              NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \
              {                                                                        \
              NS_ASSERTION(aInstancePtr, “QueryInterface requires a non-NULL destination!”); \
              if ( !aInstancePtr )                                                   \
              return NS_ERROR_NULL_POINTER;                                        \
              nsISupports* foundInterface;

              The code in this macro doesn’t really do any work. It just provides the function’s declaratory preamble and some lightweight error checking in the form of a test for a null return pointer. There isn’t even a closing curly brace to complete the function so it’s logical to suspect this macro is intended to be followed by other macros that fill in rest of the code. This next macro does some of that work. NS_INTERFACE_MAP_ENTRY is just an alias for NS_IMPL_QUERY_BODY.
              Listing 12. NS_IMPL_QUERY_BODY

              #define NS_IMPL_QUERY_BODY(_interface)
              if ( aIID.Equals(NS_GET_IID(_interface)) )
              foundInterface = NS_STATIC_CAST(_interface*, this);
              else

              This is the critical snippet of code that does the matching for our interface map. Because of the way the if/else statements are structured, we can stack multiple NS_IMPL_QUERY_BODY macros in succession to build an interface map that will answer to any number of interface IDs. The next macro, NS_INTERFACE_MAP_ENTRY_AMBIGUOUS, is just an alias for NS_IMPL_QUERY_BODY_AMBIGUOUS.
              Listing 13. NS_IMPL_QUERY_BODY_AMBIGUOUS

              #define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass)             \
              if ( aIID.Equals(NS_GET_IID(_interface)) )                             \
              foundInterface = NS_STATIC_CAST(_interface*, NS_STATIC_CAST(_implClass*, this)); \
              else

              NS_IMPL_QUERY_BODY_AMBIGUOUS looks like it is doing the same work as NS_IMPL_QUERY_BODY — which it is. The only extra work being done here is avoiding a compiler error when trying to return an interface pointer for nsISupports when there are two or more supported interfaces that derive from nsISupports. Any one of them is also a valid nsISupports interface pointer — so the dilemma for the C++ compiler is to choose which one. One requirement placed on an XPCOM interface-dispensing mechanism is that it always return the same pointer for the same interface ID — so this macro also helps to comply with this rule by specifying which nsISupports-derived interface pointer gets to be used as the nsISupports interface pointer. As Listing 14 illustrates, NS_INTERFACE_MAP_END is just an alias for NS_IMPL_QUERY_TAIL_GUTS.
              Listing 14. NS_IMPL_QUERY_TAIL_GUTS

              #define NS_IMPL_QUERY_TAIL_GUTS
              foundInterface = 0;
              nsresult status;
              if ( !foundInterface )
              status = NS_NOINTERFACE;
              else
              {
              NS_ADDREF(foundInterface);
              status = NS_OK;
              }
              *aInstancePtr = foundInterface;
              return status;
              }

              At long last, we get to the end of the implementation of QueryInterface. This last snippet of code returns an error code of NS_NOINTERFACE if the caller’s interface ID does not match any of the IDs in its map. If the caller’s interface ID matches one of the object’s supported interfaces, the code calls the object’s AddRef method and returns a pointer to the interface along with a result code of NS_OK.
              The code we’ve just gone through is a stock implementation of nsISupports for a component with a single interface. The stock implementations for supporting multiple interfaces are similar. Actual components may be written using one of the stock implementations or they may provide their own. In most cases, they will use the macros found in nsISupportsUtils.h. By now you should see why it is so important to be able to dissect C style macros — particularly those with nested definitions — if you want to be able to read and understand the mozilla/XPCOM code base.