IBM®
Skip to main content
    Country/region [select]      Terms of use
 
 
    
     Home      Products      Services & industry solutions      Support & downloads      My IBM     
skip to main content

developerWorks  >  SOA and Web services  >

XPCOM Part 5: Implementation

Coding, installing, and testing XPCOM components

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Introductory

Rick Parrish (rfmobile@swbell.net), Independent consultant, Freelance

01 Jun 2001

This final installment of a five-part introduction to XPCOM brings it all together. Rick Parrish puts the final touches on our example XPCOM implementation, shows you how to install the component, and demonstrates simple testing methods using XPCShell.

XPCOM stands for Cross Platform Component Object Model. The purpose of XPCOM is to provide a mechanism for writing modular software objects called components over a large number of platforms. This fifth and final installment discusses issues and offers suggestions related to coding an implementation.

Implementation

Notice that while the XPIDL-generated C++ stub code in Part 4 of this series (see Resources) does a lot of the grunt work as far as handling member function definitions, it does not provide a real implementation. Almost every generated function is coded to return a "not implemented" error. Technically speaking, this is a valid implementation (although for our purposes, it's not very interesting). Obviously, after verifying that our stub-implemented XPCOM component can be built, we'll need to go back and fill in some code that provides a more useful implementation.

Most XPCOM components will have at least a few attributes that are what I would call transparent. These are read-write attributes of just about any type that have both get and set accessors. On a transparent attribute, calling the get accessor after the set accessor will always return the same value. Nothing short of destroying the XPCOM component will change that.

Suppose we have the attribute attribute string name; in our IDL. The code to implement this as a transparent attribute might look like the code in Listing 1.


NS_IMETHODIMP mozSomeImpl::GetName(char** aName)
{
    if (!aName)
       return NS_ERROR_NULL_POINTER;

    *aName = nsnull;
    if (mName)
    {
       *aName = PL_strdup(mName);
       if (!*aName)
          return NS_ERROR_OUT_OF_MEMORY;
    }

    return NS_OK;
}

NS_IMETHODIMP mozSomeImpl::SetName(const char* aName)
{
    if (!aName)
       return NS_ERROR_NULL_POINTER;
    if (mName)
       PL_strfree(mName);
    mName = PL_strdup(aName);
    if (!mName)
       return NS_ERROR_OUT_OF_MEMORY;
    return NS_OK;
}

A note about the code

Listing 1 assumes class mozSomeImpl has a char* private data member named mName. It also assumes that mName was initialized to NULL in the constructor. It's a good idea to initialize any private data to some known value. The example in Listing 1 uses a string data type that requires some heap allocation to create the strings. It also assumes that a NULL string pointer is an error. However, in some situations it might be desirable for NULL to represent an empty string. The code would be even shorter for a simpler attribute type such as bool, int, or double.

Some situations will call for an attribute whose data type is another XPCOM component. The data member used to store the attribute should be a smart pointer rather than a plain-interface pointer to keep track of this attribute's value. When the XPCOM component is destroyed, its implementation will call the smart pointer's destructor and properly release its reference to the other component.

Speaking of destructors, if you look back to the XPIDL-generated C++ class you can see that the class's destructor is declared virtual. Make sure that your XPCOM component's implementation does the same.



Back to top


Module registration

Our XPCOM component will need to be linked into a library file, which is what the component manager calls a module. That module could contain only one XPCOM component or it could play host to a large number of components. When the component manager discovers our new module, it will query the module to determine what XPCOM components it provides. The name of the library file along with all the contract IDs and class IDs will be placed into a small component registry database for fast retrieval. XPCOM dictates a simple API that every XPCOM module must provide so that the component manager can do its job.

It really is quite easy -- two static C functions, one static C structure array, and a pair of macro expansions (and, optionally, one additional macro expansion for each additional XPCOM component that shares this module). Don't believe how easy it is? See the complete module source code in Listing 2.


#include "nsIGenericFactory.h"
#include "mozGPS.h"

// macro expansion defines our factory constructor method
// used by the components[] array below.
NS_GENERIC_FACTORY_CONSTRUCTOR(mozGPSImpl)

// pair of static register/unregister functions.
static NS_METHOD mozGPSRegistrationProc(nsIComponentManager *aCompMgr,
    nsIFile *aPath, const char *registryLocation, const char *componentType)
{
    // Registration specific activity goes here
    return NS_OK;
}

static NS_METHOD mozGPSUnregistrationProc(
    nsIComponentManager *aCompMgr, nsIFile *aPath,
    const char *registryLocation)
{
    // Un-registration specific activity goes here
    return NS_OK;
}

// Here's the structure array I was talking about.
static nsModuleComponentInfo components[] =
{
    {
       "GPS Component", NS_GPS_CID,
       NS_GPS_CONTRACTID,
       mozGPSImplConstructor,
       mozGPSRegistrationProc, /* NULL if not needed. */
       mozGPSUnregistrationProc /* NULL if not needed. */
    }
};
// One last macro expansion to export NSGetModule() so
// that the component manager can find us.
NS_IMPL_NSGETMODULE("mozGPSModule", components)


A note about the code

The code in Listing 2 takes advantage of nsIGenericFactory to describe the module to the component manager. In this example, mozGPSImpl is the name of the C++ class that implements the nsIGPS interface. That C++ class, along with a class ID and a contract ID, are defined in mozGPS.h. In this example the C++ components[] array only has one item. If we were writing a module that provided two XPCOM components, we could use the code shown in Listing 3. It should be fairly obvious how to add a third and fourth component to the module.


#include "nsIGenericFactory.h"
#include "mozFirst.h"
#include "mozSecond.h"

NS_GENERIC_FACTORY_CONSTRUCTOR(mozFirstImpl)
NS_GENERIC_FACTORY_CONSTRUCTOR(mozSecondImpl)

// NOTE: mozFirst[Un]RegistrationProc &
mozSecond[Un]RegistrationProc are completely optional.
static nsModuleComponentInfo components[] =
{
    {
       "First Component",
       NS_FIRST_CID, NS_FIRST_CONTRACTID,
       mozFirstImplConstructor,
       mozFirstRegistrationProc, /* NULL if not needed. */
       mozFirstUnregistrationProc /* NULL if not needed. */
    },
    {
       "Second Component",
       NS_SECOND_CID, NS_SECOND_CONTRACTID,
       mozSecondImplConstructor,
       mozSecondRegistrationProc, /* NULL if not needed. */
       mozSecondUnregistrationProc /* NULL if not needed. */
    }
};
NS_IMPL_NSGETMODULE("mozSomeModule", components)




Back to top


Makefile

Most programmers are familiar with using some form of make utility to manage compiling and linking software. Most fancy IDEs (integrated development environments) do nothing more than combine a text editor and a makefile viewer/editor into an attractive user interface. Some platforms have this and others don't. Since this is a cross-platform environment, most programmers take the least common denominator approach and release their source code along with a simple text-based makefile usable by most any common command-line make utility. Here is a makefile for this project that will manage compiling the IDL source and C++ sources to produce a binary library and XPT files.

DEPTH=..\..
MODULE=GPS
MAKE_OBJ_TYPE=DLL
DLLNAME=$(MODULE)
DLL=.\$(OBJDIR)\$(DLLNAME).dll
XPIDLSRCS=.\nsIGPS.idl \
           $(NULL)
CPP_OBJS=\
.\$(OBJDIR)\mozGPS.obj \
.\$(OBJDIR)\mozGPSModule.obj \
$(NULL)

LLIBS=\
$(DIST)\lib\xpcom.lib \
$(LIBPLC) \
$(NULL)
LINCS=\
-I$(PUBLIC)\GPS \
-I$(PUBLIC)\raptor \
-I$(PUBLIC)\xpcom \
$(NULL)

include <$(DEPTH)\config\rules.mak>
install:: $(DLL)
    $(MAKE_INSTALL) $(DLL) $(DIST)\bin\components


On Windows, run nmake from the project directory. On most other operating systems (like Linux and Unix) the command is make.

At this point, everything should build, even though we haven't started doing any real work on the implementation. It's a good idea to do a build now to make sure that there aren't any unexpected surprises in the mostly IDL-generated C++ code. If there are, we can go back now and fix them in the IDL before having invested a lot of time writing some C++ code that needs to be scrapped or mangled because the interface has changed to correct an error.



Back to top


Installation

Installing an XPCOM component is very easy. Just copy the DLL or shared library and the XPIDL compiler-generated XPT file to the components directory and run a tool called regxpcom. That's it. If you are using the debug build of Mozilla for testing, you can leave this last step out. Mozilla calls the component manager's AutoRegister method to take care of discovering and registering the new component. In some cases, the registration mechanism can become confused -- particularly when you are replacing an existing module. An easy way to force the component manager to rebuild its component registry is to delete the two files it creates in the components directory.



Back to top


Testing with XPCShell

Once installation is done, you'll end up with a shared library (.so or .dll) file and an XPT file. The XPT file contains the type library information needed by JavaScript. To test these, just copy them to the components directory where you have Mozilla installed. You don't need to load Mozilla to test out your component. In fact, I suggest you don't unless you are writing chrome-related components.

Writing a piece of JavaScript to load your component and exercise each of its properties and methods is an easier way to go. You can use XPCShell, which is a little command-line utility that gives you a JavaScript command line. To run your test script, use the load command as in:

load('test.js');

Being able to use JavaScript for testing is nice because it reflects the actual nature of testing and debugging. You can test your component in new ways as fast as you can think of (and type) them instead of writing and compiling a separate C or C++ program to do the same job. Listing 5 is a test script that could be cut out and saved into a file (like test.js above).

const kClass = "@fictitious.org/GPS;1";
var obj = components.classes[kClass].createInstance();
obj = obj.QueryInterface(Components.interfaces.nsIGPS);
dump("obj = " + obj + "\n");

If you run this on our component before actually adding code to the C++ implementation you'll likely get an error in the dump statement above since every property and method of the component has been coded to do nothing but return a "not implemented" error.



Back to top


Development

Here's where the real work takes place and where it pays to be familiar with some of the limitations of XPCOM as a framework. First and foremost, XPCOM components are not required to be reentrant. Second, most XPCOM clients use little or no multithreading. Third, when you do employ additional worker threads in your design, try to have the thread that created a component also be the one that destroys it. This is not an absolute requirement, but it will eliminate a few debug build error messages from some heap management code currently part of the XPCOM libraries.

It is possible to create an XPCOM component that is assigned to a thread -- possibly along with other components. Any other thread wishing to access the component must do so through a proxy. In the case of calling methods and attribute setters where no data is returned, this can be done asynchronously by sending a message to the component's owning thread (which is handled for us by the proxy code). However, in cases where something is being retrieved or a result returned from the component, the calling thread may become blocked momentarily while it's waiting for the owning thread to perform the requested operation and return the results.

If you're a Windows programmer and are fond of sprinkling MessageBox calls in your code, try to avoid this unless you are certain that your component will only be used with Mozilla or some other application that actually has a message loop. XPCShell is text-based so it does not have a message loop for processing Windows messages. As a consequence, any dialog box will pop up and then freeze. You can call fprintf() to stdout or stderr instead. In general, avoid making calls to Win32 APIs unless you intend for your component to be platform specific. The Netscape Portable Runtime (NSPR) is a C library that handles most basic OS resource requests like memory allocation, file management, and thread synchronization. XPCOM is implemented using NSPR, which is where XPCOM gets most of its portability.

XPConnect combined with XPIDL does a fine job of making an interface visible from JavaScript. However, there are some data types that are not easily supported and there are some JavaScript syntaxes that require explicit calls to the JavaScript engine. One example is an attribute that is really a container. If the container holds only simple types or another object, you can declare a read-only interface that accepts some sort of data type as the container index. If, however, you want to accept more than one data type as an in or out parameter, you'll need to do some JavaScript engine magic.

The JS engine supports a variant data type called a jsval, but I haven't had any success with describing a jsval in XPIDL. Having XPIDL recognize a jsval as a variant data type would be a definite plus for me. There are a number of applications where having a method accept a variant parameter or having a function return a variant result would be very useful. A good example would be browsing a database where the data types for the columns in the tables is unknown prior to connecting to the database.

Returning to the container property example: If you want to use the array operators ("[" and "]") to specify an array index, then you'll need to learn some of the workings of the JS engine. In short, to leverage the most out of XPConnect's ability to map an IDL interface into JavaScript, just avoid these two situations.

For those of you who have done your share of VBScript and JScript programming -- perhaps in an ASP or VBA environment -- you will appreciate the fact that XPConnect makes QueryInterface() visible. This allows you to switch interfaces on a component that supports multiple interfaces. Although there is a hack to get around this, ordinary JScript and VBScript are forced to work with whatever is declared as the default interface for a scriptable component.

There is also an effort underway to produce a "flattened" interface model for scriptable components. This will allow JavaScript programmers to call a method without worrying about first QIing for the appropriate interface to which the method belongs. This may mean a QueryInterface-free future for scripting.

At some point in developing your component you'll inevitably discover the need to change the interface to do something like add a property or method or change a parameter type. A small change won't invalidate your work-in-progress implementation. The following steps shouldn't put too much strain on your ability to cope with chaos: edit the IDL and let the XPIDL compiler make a new C++ header file; cut and paste the portions that have changed to your implementation file; and recompile to make sure IDL, header, and CPP files are in agreement.



Back to top


Threads and proxies

One last consideration in your implementation is how you intend to deal with reentrancy. Suppose some other application that is multithreaded wants to make use of your component. You have two options: implement a fully reentrant component, or implement a non-reentrant component. An application or component that is multithreaded must do some extra work to use a single threaded component.

If you ignore the threading model expected by another component you will soon be fighting a losing battle against race conditions and debug assertions complaining about components and memory being allocated and released from different threads.

The first option of implementing a fully reentrant component can be done by using the semaphore and mutex features of NSPR -- the portable runtime that XPCOM is layered upon (see Resources). The API for NSPR is well documented so I won't go into detail about that here. You will want to look at the thread-safe versions of the implementing macros for nsISupports.

The second option is to implement your component with the assumption that only a single thread will be using it. Most of the Mozilla components are written this way. Since there isn't any extra effort added to the implementation I'll describe how a threaded application or component (the client code) would go about accessing such a non-reentrant component. XPCOM includes a proxy manager that is able to examine the type information of an installed component to create a proxy interface that impersonates the component by capturing the parameters and passing them to the component in a thread-safe manner.

The proxy manager component offers two methods: one for creating a proxied component (it creates the component and the proxy) called GetProxy, and another for creating a proxy for an existing component called GetProxyForObject. Both methods expect a pointer to an event queue, which you can request from the event queue service.

I must emphasize here that this proxy mechanism works only for components that were created from the ground up using the xpidl compiler. They must have type library information so the proxy manager can impersonate the interfaces on the fly. The proxy manager is only intended to proxy across thread boundaries inside the same application, not across processes or across a network.



Back to top


Conclusion

This concludes the five-part XPCOM article series. Once you become comfortable with the basic mechanics of writing your components, you may want to explore some of the more advanced areas of component development with XPCOM, such as:

In the meantime, take what you've learned here and go build something.



Resources



About the author

Rick Parrish has held an interest in computers since high school and in electronics even longer. He originally pursued an education in electrical engineering but discovered that software, unlike hardware, did not require smelly vats of ferric chloride or run the risk of burnt fingers just to perform a design change. Rick has been programming in C/C++ for A LONG TIME(tm) but has also done heaps of work in VB, Delphi(Pascal), and a handful of assembly languages. He still manages to squeeze in a project or two that requires hot solder. His current opinions are that while Windows 2000 is neat-o, Ogg Vorbis is cool, and the Linux 2.4 kernel with IPTables definitely rocks! He is currently interested in starting an open source project to develop tools for software modeling design. Rick can be reached at rfmobile@swbell.net.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top



    About IBM Privacy Contact