Modularization techniques

Warning: The content of this article may be out of date. It was last updated in 2004.

Introduction

The purpose of this document is provide all the information you need to create a new Mozilla Module or break existing code into a module. The mechanism we're using is based on the principles laid down by COM, so pretty much anything you know about COM can be applied here, and any reference on COM can provide you with more interesting and complex examples than the ones provided here.

The Basics

Interfaces

The basic building blocks of modules are C++ pure virtual interfaces. A pure virtual interface is simply a class where every method is defined as pure virtual, that is:

virtual int foo(int bar) = 0;

Pure virtual interfaces provide an easy mechanism for passing function tables between modules that may reside in separate, possibly dynamically loaded, libraries. Each interface is assigned a unique Interface Identifier, or IID.

nsISupports

The key interface in our model is the nsISupports interface, our equivalent to COM's IUnknown interface. nsISupports provides two key features, interface interrogation and reference counting. Interface interrogation is a simple, uniform mechanism for determining which interfaces a object supports, and for hiding the the mechanics of how the object was implemented.

Interface interrogation is performed using the QueryInterface() method. The caller passes in an ID and a pointer to a address to place the resulting interface. If the query is successful, QueryInterface() will return NS_OK. If the object does not support the given interface, it will return NS_NOINTERFACE.

Reference counting is performed using the AddRef() and Release() methods. An objects reference count generally starts at zero. AddRef() increments that reference count, and Release() decrements it. If a call to Release() causes the reference count to hit zero, the object will generally free itself. A successful QueryInterface() will call AddRef() on the requested interface before returning. Both AddRef() and Release() return the resulting reference count.

The convenience macros NS_ADDREF() and NS_RELEASE() are preferred over calling AddRef and Release directly. In debug builds, these macros provide useful reference counting logs. Use them wherever possible.

/*
 * The nsISupports interface
 */

class nsISupports {
public:
    NS_IMETHOD QueryInterface(const nsIID &aIID,
                              void **aResult) = 0;
    NS_IMETHOD_(nsrefcnt) AddRef(void) = 0;
    NS_IMETHOD_(nsrefcnt) Release(void) = 0;
};

The NS_IMETHOD and NS_IMETHOD_(type) macros are basically shorthand for virtual nsresult and virtualtype. On Windows they expand to virtual nsresult __stdcall and virtual type __stdcall for COM compatibility reasons. You don't have to use them in your interfaces unless you're concerned with COM compatibility.

All Mozilla interfaces inherit from nsISupports. Inheriting from nsISupports allows any interface to be interrogated about other interfaces that its instance may support, and insures that reference counting facilities are always available. The IID for nsISupports is defined as NS_ISUPPORTS_IID.

QueryInterface() has several important characteristics that must be maintained. If you perform a QueryInterface() on interface A and obtain interface B, you must be able to perform a QueryInterface() B and obtain interface A. If interfaces A and B are both implemented by the same instance, performing a QueryInterface() for nsISupports on either should return the exact same interface. This means that even though interface B inherits from nsISupports, performing a QueryInterface() on it may not return the same interface. This important behavior is the only reliable mechanism for determining whether interfaces A and B are implemented by the same object.For simple objects, maintaining these behaviors is easy. Aggregation, as we will see later, can complicate things.

On the other hand, objects are allowed a certain degree of flexibility in their implementations of AddRef() and Release(). They can maintain a single reference count for the entire object, or individual reference counts for each interface. A static object would chose to ignore reference counts altogether. However, a poor implementation of these functions can have negative results, such as memory leaks or inadvertent access of freed objects.

Factories

Factories are special classes dedicated to creating instances of classes. A Foo class will typically have a FooFactory associated with it. The nsIFactory interface is the equivalent of COM's IClassFactory.

/*
 * The nsIFactory interface
 */

class nsIFactory: public nsISupports {
public:
    NS_IMETHOD CreateInstance(nsISupports *aOuter,
                              const nsIID &aIID,
                              void **aResult) = 0;
    NS_IMETHOD LockFactory(PRBool aLock) = 0;

The reason for using factories is that it provides a mechanism for creating an object without having access to the class declaration for that object. Calling new Foo() requires that at compile time you have access to the class declaration of Foo(). A factory allows an implementor to hide both the class declaration and creation details of an object, an extremely important step for allowing maximum flexibility in the implementation of a class and reducing compile time dependencies. It can even be used to eliminate all link time dependencies on the class and its factory entirely.

The Component Manager

One of the major goals of our modularization is to remove link time dependencies. So how do you find a module if you've never linked with it? We've created something called the nsComponentManager. The nsComponentManager is simply a mapping of class IDs to factories and their containing libraries.

class nsComponentManager {
public:
  // Finds a factory for a specific class ID
  static nsresult FindFactory(const nsCID &aClass,
                              nsIFactory **aFactory);
  // Creates a class instance for a specific class ID
  static nsresult CreateInstance(const nsCID &aClass,
                                 const nsIID &aIID,
                                 nsISupports *aDelegate,
                                 void **aResult);
  // Manually registry a factory for a class
  static nsresult RegisterFactory(const nsCID &aClass,
                                  nsIFactory *aFactory,
                                  PRBool aReplace);
  // Manually registry a dynamically loaded factory for a class
  static nsresult RegisterFactory(const nsCID &aClass,
                                  const char *aLibrary,
                                  PRBool aReplace,
                                  PRBool aPersist);
  // Manually unregister a factory for a class
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    nsIFactory *aFactory);
  // Manually unregister a dynamically loaded factory for a class
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    const char *aLibrary);
  // Unload dynamically loaded factories that are not in use
  static nsresult FreeLibraries();
};

There are several ways a factory can make its way into the repository. The most direct is through RegisterFactory(). RegisterFactory() supports two different registration mechanisms. The first takes a class ID and a pointer to a factory. This mechanism can be used on factories that are linked into the executable. The second takes a class ID and the path to a dynamically loadable library. This mechanism can be used both inside an executable at run-time and externally using the aPersist flag to tell the repository to store the class ID/library relationship in its permenant store.

About nsIIDs and nsCIDs

To simplify the process of dynamically finding, loading and binding interfaces, all classes and interfaces are assigned unique IDs. The IDs are unique 128 bit numbers that are based on UUIDs. For those who like gory details, their structure is this:

struct nsID {
  PRUint32 m0;
  PRUint16 m1, m2;
  PRUint8 m3[8];
};

Frequently you see them represented as strings, like this:

{221ffe10-ae3c-11d1-b66c-00805f8a2676}

To initialize an ID struct you declare them like this:

ID = {0x221ffe10, 0xae3c, 0x11d1,
       {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}};

Why the b66c couplet gets broken up and grouped with the last set of bytes is probably a footnote somewhere. On Windows you can use the programs uuidgen and guidgen, which ship with Visual C++, to generate IDs.

A Simple Example

It is recommended that you use XPIDL to define your interfaces. This sample code should be updated to reflect this, but it gives you a good basic understanding of COM from the C++ perspective.

File nsISample.h

nsISample.h defines an extremely simple interface and its interface ID (IID). The important things to notice are that the interface inherits from nsISupports, and all member functions are pure virtual methods.

#include "nsISupports.h"

// {57ecad90-ae1a-11d1-b66c-00805f8a2676}
#define NS_ISAMPLE_IID \
{0x57ecad90, 0xae1a, 0x11d1, \
  {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}}

/*
 * nsISample Interface declaration
 */

class nsISample: public nsISupports {
public:
  NS_IMETHOD Hello() = 0;
};

File nsSample.h

nsSample.h defines the class ID (CID) for our sample class. Note that one interface can have a number of classes that implement it, so there is not necessarily a one-to-one mapping from IIDs to CIDs. It also defines the function for retrieving our class factory. Notice it does not contain a class declaration.

#include "nsIFactory.h"

// {d3944dd0-ae1a-11d1-b66c-00805f8a2676}
#define NS_SAMPLE_CID \
 {0xd3944dd0, 0xae1a, 0x11d1, \
   {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}}

extern nsresult GetSampleFactory(nsIFactory **aResult);

File nsSample.cpp

nsSample.cpp contains both the declaration and implementation of our sample class, and the declaration and implementation of our class factory.

#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kIFactoryIID, NS_IFACTORY_IID);
static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);
static NS_DEFINE_CID(kISampleCID, NS_ISAMPLE_CID);

/*
 * nsSampleClass Declaration
 */

class nsSample: public nsISample {
private:
  nsrefcnt mRefCnt;

public:
// Constructor and Destuctor
  nsSample();
  ~nsSample();

// nsISupports methods
  NS_IMETHOD QueryInterface(const nsIID &aIID,
                            void **aResult);
  NS_IMETHOD_(nsrefcnt) AddRef(void);
  NS_IMETHOD_(nsrefcnt) Release(void);

// nsISample method
  NS_IMETHOD Hello();
};

/*
 * nsSampleFactory Declaration
 */

class nsSampleFactory: public nsIFactory {
private:
  nsrefcnt mRefCnt;

public:
  nsSampleFactory();
  ~nsSampleFactory();

// nsISupports methods
  NS_IMETHOD QueryInterface(const nsIID &aIID,
                            void **aResult);
  NS_IMETHOD_(nsrefcnt) AddRef(void);
  NS_IMETHOD_(nsrefcnt) Release(void);

// nsIFactory methods
  NS_IMETHOD CreateInstance(nsISupports *aOuter,
                            const nsIID &aIID,
                            void **aResult);

  NS_IMETHOD_(void) LockFactory(PRBool aLock);
};

/*
 * nsSample Implementation
 */

nsSample::nsSample()
{
  mRefCnt = 0;
}

nsSample::~nsSample()
{
  assert(mRefCnt == 0);
}

NS_IMETHOD nsSample::QueryInterface(const nsIID &aIID,
                                  void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  // Always NULL result, in case of failure
  *aResult = NULL;

  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  } else if (aIID.Equals(kISampleIID)) {
    *aResult = (void *) this;
  }

  if (aResult != NULL) {
    return NS_ERROR_NO_INTERFACE;
  }

  AddRef();
  return NS_OK;
}

nsRefCount nsSample::AddRef()
{
  return ++mRefCnt;
}

nsRefCount nsSample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0; // Don't access mRefCnt after deleting!
  }
  return mRefCnt;
}

/*
 * nsSampleFactory Implementation
 */

nsSampleFactory::nsSampleFactory()
{
  mRefCnt = 0;
}

nsSampleFactory::~nsSampleFactory()
{
  assert(mRefCnt == 0);
}

NS_IMETHODIMP nsSampleFactory::QueryInterface(const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  // Always NULL result, in case of failure
  *aResult = NULL;

  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  } else if (aIID.Equals(kIFactoryIID)) {
    *aResult = (void *) this;
  }

  if (*aResult == NULL) {
    return NS_ERROR_NO_INTERFACE;
  }

  AddRef(); // Increase reference count for caller
  return NS_OK;
}

NS_IMETHODIMP(nsRefCount) nsSampleFactory::AddRef()
{
  return ++mRefCnt;
}

NS_IMETHODIMP(nsRefCount) nsSampleFactory::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0; // Don't access mRefCnt after deleting!
  }
  return mRefCnt;
}

NS_IMETHODIMP nsSampleFactory::CreateInstance(nsISupports *aOuter,
                                         const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports inst = new nsSample();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(aIID, aResult);

  if (res != NS_OK) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

void nsSampleFactory::LockFactory(PRBool aLock)
{
  // Not implemented in simplest case.
}

nsresult GetSampleFactory(nsIFactory **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports inst = new nsSampleFactory();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(kIFactoryIID, aResult);

  if (res != NS_OK) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

File main.cpp

main.cpp is a simple program that creates an instance of our sample class and disposes of it. Because it obtains the class factory directly, it doesn't use the CID for class.

#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);

int main(int argc, char *argv[])
{
  nsIFactory *factory;
  GetSampleFactory(&factory);

  nsISample *sample;

  nsresult res = factory->CreateInstance(NULL, kISampleIID,
                                         (void **) &sample);

  if (res == NS_OK) {
    sample->Hello();
    NS_RELEASE(sample);
  }

  return 0;
}

Moving to a Dynamically Linked Library

Implementing a DLL

Once you've set a factory, moving it to a DLL is a relatively trivial thing. A DLL that contains a factory need to define one or two exported functions:

// Returns the factory associated with the given class ID
extern "C" NS_EXPORT nsresult NSGetFactory(const nsCID &aCID,
                                           nsIFactory **aFactory);

// Returns whether the DLL can be unloaded now.
extern "C" NS_EXPORT PRBool NSCanUnload();

The implementation of NSGetFactory() in the simplest case is nearly identical to that of GetSampleFactory() in our previous example. You only need to verify that the class ID passed in is the correct one for the factory you implement. If your DLL contains multiple factories, you'll need to add conditional code to determine which one to return.

NSCanUnload() is an optional, but useful function. If implemented, it allows the NSRepository to free up memory by unloading DLLs it is no longer using when FreeLibraries() is called. The implementation takes into consideration two things when deciding whether or not a DLL can be unloaded: Whether any of its factories are currently in use, and whether anyone has locked the server. If NSCanUnload() is not implemented, the DLL will not be unloaded.

The following example turns nsSample.cpp into a file that can be compiled into a DLL. The differences are marked with strong emphasis. There really aren't that many.

File nsSample3.cpp

#include <iostream.h>
#include "pratom.h"
#include "nsRepository.h"
#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kIFactoryIID, NS_IFACTORY_IID);
static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);
static NS_DEFINE_CID(kSampleCID, NS_SAMPLE_CID);

<strong>/*
 * Globals
 */

static PRInt32 gLockCnt = 0;
static PRInt32 gInstanceCnt = 0;</strong>

/*
 * nsSampleClass Declaration
 */

class nsSample: public nsISample {
private:
  nsrefcnt mRefCnt;

public:
// Constructor and Destuctor
  nsSample();
  ~nsSample();

// nsISupports methods
  NS_IMETHOD QueryInterface(const nsIID &aIID,
                            void **aResult);
  NS_IMETHOD_(nsrefcnt) AddRef(void);
  NS_IMETHOD_(nsrefcnt) Release(void);

// nsISample method
  NS_IMETHOD Hello();
};

/*
 * nsSampleFactory Declaration
 */

class nsSampleFactory: public nsIFactory {
private:
  nsrefcnt mRefCnt;

public:
  nsSampleFactory();
  ~nsSampleFactory();

// nsISupports methods
  NS_IMETHOD QueryInterface(const nsIID &aIID,
                            void **aResult);
  NS_IMETHOD_(nsrefcnt) AddRef(void);
  NS_IMETHOD_(nsrefcnt) Release(void);

// nsIFactory methods
  NS_IMETHOD CreateInstance(nsISupports *aOuter,
                                  const nsIID &aIID,
                                  void **aResult);

  NS_IMETHOD_(void) LockFactory(PRBool aLock);
};

/*
 * nsSample Implemtation
 */

nsSample::nsSample()
{
  mRefCnt = 0;
  <strong>PR_AtomicIncrement(&gInstanceCnt);</strong>
}

nsSample::~nsSample()
{
// assert(mRefCnt == 0);
  <strong>PR_AtomicDecrement(&gInstanceCnt);</strong>
}

NS_IMETHODIMP nsSample::Hello() {
  cout << "Hello, world\n";

  return NS_OK;
}

NS_IMETHODIMP nsSample::QueryInterface(const nsIID &aIID,
                                  void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  // Always NULL result, in case of failure
  *aResult = NULL;

  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  } else if (aIID.Equals(kISampleIID)) {
    *aResult = (void *) this;
  }

  if (aResult != NULL) {
    return NS_NOINTERFACE;
  }

  AddRef();
  return NS_OK;
}

NS_IMETHODIMP nsSample::AddRef()
{
  return ++mRefCnt;
}

NS_IMETHODIMP nsSample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0; // Don't access mRefCnt after deleting!
  }
  return mRefCnt;
}

/*
 * nsSampleFactory Implementation
 */

nsSampleFactory::nsSampleFactory()
{
  mRefCnt = 0;
  <strong>PR_AtomicIncrement(&gInstanceCnt);</strong>
}

nsSampleFactory::~nsSampleFactory()
{
// assert(mRefCnt == 0);
  <strong>PR_AtomicDecrement(&gInstanceCnt);</strong>
}

NS_IMETHODIMP nsSampleFactory::QueryInterface(const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  // Always NULL result, in case of failure
  *aResult = NULL;

  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  } else if (aIID.Equals(kIFactoryIID)) {
    *aResult = (void *) this;
  }

  if (*aResult == NULL) {
    return NS_NOINTERFACE;
  }

  AddRef(); // Increase reference count for caller
  return NS_OK;
}

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::AddRef()
{
  return ++mRefCnt;
}

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0; // Don't access mRefCnt after deleting!
  }
  return mRefCnt;
}

NS_IMETHODIMP nsSampleFactory::CreateInstance(nsISupports *aOuter,
                                         const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports *inst = new nsSample();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(aIID, aResult);

  if (res != NS_OK) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

<strong>/*
 * Exported functions
 */

void nsSampleFactory::LockFactory(PRBool aLock)
{
  if (aLock) {
    PR_AtomicIncrement(&gLockCnt);
  } else {
    PR_AtomicDecrement(&gLockCnt);
  }
}

extern "C" NS_EXPORT nsresult NSGetFactory(const nsCID &aCID,
                                           nsIFactory **aResult)</strong>
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports *inst;

  <strong>if (aCID.Equals(kSampleCID)) {
    inst = new nsSampleFactory();
  } else {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }</strong>

  nsresult res = inst->QueryInterface(kIFactoryIID, (void **) aResult);

  if (res != NS_OK) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

<strong>extern "C" NS_EXPORT PRBool NSCanUnload()
{
  return PRBool(gInstanceCnt == 0 && gLockCnt == 0);
}</strong>

Now, instead of directly calling the factory, we call NSRepository::CreateInstance(). We rely on the factory registering itself somehow.

File: main2.cpp

#include "nsRepository.h"
#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);
static NS_DEFINE_CID(kSampleCID, NS_SAMPLE_CID);

int main(int argc, char *argv[])
{
  nsISample *sample;

  nsresult res = NSRepository::CreateInstance(kSampleCID,
                                              NULL,
                                              kISampleIID,
                                              (void **) &sample);

  if (res == NS_OK) {
    sample->Hello();
    NS_RELEASE(sample);
  }

  return 0;
}

Registering a DLL

This is currently being hashed out. You can currently manually register a DLL using the NSRepository's RegisterFactory() method (For an example of this, see nsSample2.cpp).

A DLL can export two additional functions for self registration and unregistration.

extern "C" NS_EXPORT nsresult NSRegisterSelf(const char *path);
extern "C" NS_EXPORT nsresult NSUnregisterSelf(const char *path);

This allows a DLL to register and unregister all its factories. A simple program called RegFactory.exe (on Windows) or regfactory (on UNIX) can be used to register self-registering DLLs.

Reference Counting Basics

Reference counting is a critical part of modularity picture. There are a number of basic reference counting rules to remember. Here's a quick summary.

Out Parameters

Functions that return a new interface should call AddRef() on that interface before returning it.

nsresult GetFoo(IFoo **aFooRes)
{
  if (aFooRes == NULL) {
    return NS_ERROR_NULL_POINTER;
  }
  *aFooRes = mFoo;
  NS_ADDREF(*aFooRes);

  return NS_OK;
}

Remember that this applies to the interfaces returned by QueryInterface(), CreateInstance() and NS_NewX(), and you must call Release() on them when you are done to avoid memory leaks.

In Parameters and Local Pointers

Interfaces that are passed in to a function and local copies of that interface pointer are assumed to be in the lifetime of the calling function, and do not need to have AddRef() called.

nsresult TweekFoo(IFoo *aFoo1, IFoo *aFoo2) {
  IFoo local = aFoo;

  if (aFoo->Bar() == NS_OK) {
    local = aFoo2;
  }

  return local->Boff();
}

In-Out Parameters

In-Out parameters are used as both In parameters and Out parameters. If a function changes the value of an interface In-Out parameter, it should call Release() on the interface passed in and AddRef() on the interface passed out.

nsresult RefreshFoo(IFoo **aFoo)
{
  if (aFoo == NULL || *aFoo == NULL) {
    return NS_ERROR_NULL_PARAMETER;
  }
  if ((*aFoo)->Stale()) {
    NS_RELEASE(*aFoo);
    *aFoo = mFoo;
    NS_ADDREF(*aFoo);
  }
  return NS_OK;
}

Global and Member Variables

Both global and member variables have lifetimes that can be changed by any number of functions. Therefore one should call AddRef() on any global or member variable that is being passed to a function, and call Release() afterward.

NS_ADDREF(mFoo);
TweekFoo(mFoo);
NS_RELEASE(mFoo);

(Soon to be) Frequently Asked Questions

Why are we mimicking COM? Doesn't COM suck?

You're probably basing this opinion on your experiences with or stories you've heard about OLE. A really important thing to remember is that COM is not OLE. OLE was built one top of COM, but it's not a shining example of COM. COM is simply a mechanism for laying out and using interfaces, the important components we've pretty much described here. OLE (actually OLE 2) was one of the first efforts to use COM.

Why C++?

C++ provides the easiest mechanism for implementing interfaces. You can manually assemble interfaces using function tables and macros, but you'd be simply doing by hand what a C++ compiler can do for you automatically.

Can I use C?

You can use C everywhere except your interface. There are mechanisms for declaring interfaces in C, but they're pretty gruesome and compiler dependent, and we're trying to make this as light weight as possible.

Why not COM?

The only platform for which COM support is currently widely available is Windows. Microsoft ships a COM extension for the Macintosh, but it's generally only installed with Internet Explorer or Microsoft Office. UNIX support for COM is scarce.

Why not COM on Windows?

Because it's not a cross platform solution, and that's what we need. We're going to make every effort to make our interfaces compatible with COM on platforms that support it, so it may not matter. But no promises, yet.

What are the major differences?

Instead of Microsoft's MIDL compiler, we are using a CORBA-compliant IDL compiler, XPIDL. It outputs NSPR types when generating C++ headers. It also generates typelibraries that are not compatible with Microsoft's .TLB format. XPCOM uses these typelibraries to allow other languages, such as JavaScript, to implement and call XPCOM objects. We also do cross-thread proxying calls using the typelib and NSPR's event queues.

Microsoft provides an extensive support infrastructure for COM. This technology is built into Windows, but not most other platforms. The technology can be licensed from Microsoft, but for obvious reasons we are not going to be doing that. In house equivalents to the important elements of this technology will be developed as needed.

Revision History

  • Feb 25, 1998, Created
  • Oct 19, 1998, Dusted off momentarily
  • Oct 10, 1999, Added comments about XPIDL, language-independentness

Original Document Information

  • Author(s): Will Scullin
  • Last Updated Date: September 13, 2004
  • Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | Details