Back to Basics: Defining XPCOM Services

Once upon a time, I asked a question in the #developers channel
on irc.mozilla.org:

How do I define that a XPCOM component is
actually a service?

The answer I received:

See if the caller uses do_GetService(), or
Components.classes[…].getService().

This is less than satisfactory. I should be able to say “There can be only
one” and not “Oh, please, don’t create more than one of me.” Over the years,
I came up with different solutions for JavaScript – and I recently discovered
the standard way to do it for C++-based XPCOM components. Read on in the
extended entry for details.

JavaScript: Manipulating XPCOMUtils.jsm

Before the introduction of XPCOMUtils.jsm, it was relatively
easy (if verbose) to define your component as a service – you defined the
module and the factories which provided a way to create each
component. The simple solution was to define a factory that wouldn’t create a
new object, but would simply return a “singleton” component – your service,
implemented not via a constructor, but as a static object.

XPCOMUtils.jsm didn’t include a path for service components,
but that’s all right. I wrote my own:

/**
* Wrap a service object for XPCOMUtils.
*
* @param aComponent The service object.
*/
function wrapServiceComponent(aComponent) {
function service() {
// do nothing; this is an empty constructor.
}
service.prototype = {
classID: aComponent.classID,
classDescription: aComponent.classDescription,
contractID: aComponent.contractID,
_xpcom_factory: {
// nsIFactory
createInstance: function createInstance(outer, iid) {
if (outer)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return aComponent.QueryInterface(iid);
}
},
get _xpcom_categories() {
if (typeof aComponent._xpcom_categories == "undefined")
return null;
return aComponent._xpcom_categories;
}
}
return service;
}

Then I can define the module like this:

var NSGetModule = XPCOMUtils.generateNSGetModule([
wrapServiceComponent(myService)
]);

C++: The service macro for factories

It’s actually even simpler to make things work for C++-based services.
First, you need to define a property of your class to hold your solo
component:

The original code posted here was wrong. See nsReadonlyWrapper.h and nsReadonlyWrapper.cpp for improved versions.

Third, you define the factory in your module:

NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFoo,
nsFoo::GetSingleton)

That’s pretty much it. Everything else for your module is the same
(including referring to nsFooConstructor), and you would define
the component itself as you normally would. The macro here ensures your
GetSingleton method will execute for every request on your
component.

6 thoughts on “Back to Basics: Defining XPCOM Services”

  1. Very good, MacLeod.
    However, I don’t see why XPCOMUtils.jsm can’t be changed to just support this out of the box.
    (From Alex: I did file a bug for that, and posted a patch… nothing.)

  2. Actually, your C++ is incorrect…
    First you must initialize mService somewhere or you will most likely crash.
    Then, what if someone calls CreateInstance on your service and releases it (the case you’re trying to avoid)? Your mService will point to freed memory and the next time any consumer calls GetService or CreateInstance you will most likely crash.
    You must double-AddRef in your first GetSingleton call and then find some way of releasing the extra reference before shutdown to avoid leaking. This is usually done via the Observer service.
    (From Alex: Thanks for the corrections – I’ll rework the example code later today.)

  3. Have/can you put this up on MDC? I was looking for it months ago and could never find the thing I needed, either in the source or in documentation.

  4. Please at least mark the C++ source code as wrong, so that people don’t copy it without reading the comments.
    And I don’t see why something that is satisfactory for the rest of the Mozilla codebase is not enough for you. You can put an assertion in the constructor that checks that the component is only instantiated once.
    Everything’s better than allow users to use createInstance if they feel like it – since this makes the client code _seem_ it creates a new instance when in fact it reuses the only one.

  5. Actually, there’s a much easier way to do this in JavaScript with a constructor like this one:

    var gFooInstance = null;
    function FooServiceConstructor() {
    if (gFooInstance) // enforce singleton/service
    return gFooInstance;
    gFooInstance = this;
     
    // whatever you also want to do in your constructor
    }

    When a constructor function returns an object, the new operator will use that returned object instead of creating a new one. So when the component’s constructor is first called you don’t return anything, but on every subsequent you return the one instance initiated at first call. (I hope this is understandable.)
    (From Alex: Oh, it’s perfectly understandable… and functionally, not much different from what I did. đŸ™‚ )

  6. Nice post!
    I looked at bugzilla, but didn’t seem to find the bug and patch you filed.
    seems it got lost.
    cheers,
    André

Comments are closed.