nsIClassInfo Overview

Mike Shaver <shaver@mozilla.org >
     Last Modified: March 12, 2001

Table of Contents


What is nsIClassInfo?

nsIClassInfo is an interface that provides information about a specific implementation class, to wit:

How do I use it?

To get the nsIClassInfo object for the implementation behind a given interface, simply QueryInterface for the nsIClassInfo interface:

C++:
nsCOMPtr<nsIClassInfo> info = do_QueryInterface(ifacePtr);

JavaScript:
var info = ifacePtr.QueryInterface(Components.interfaces.nsIClassInfo);

It's important to note that this will usually return a singleton object: there often exists only one nsIClassInfo implementation for all implementations of a given class. This is very important, because it means that you can't QueryInterface back to the original object, no matter how hard you try.

Doesn't that break the COM QueryInterface rules?

Quite. As discussed in bug 67699, though, it's a reasonable concession to make in order to avoid an additional vtbl entry on every class that wants to export nsIClassInfo data.

What are the "language helpers" for?

The language helpers are basically hooks for alternate language bindings to use for providing language-specific wrapping and manipulation behaviour, without adding code to every wrapped object.  In bug 67669, jband tells us what XPConnect does with the language helpers:

When wrapping an object xpconnect QIs the object for its classinfo (say 'foo'). If it has seen that foo classinfo pointer before then it knows that this object is a foo and it can reuse all the info is knows about foo objects. If it has never seen foo it will gather info (such as the JS helper) and remember that for future wrappings of foo objects.

Assuming that the foo helper tells xpconnect to not bother QI'ing each foo instance for a per instance helper (or if the instances don't respond to QI for the helper) then the same foo helper is used on all calls from JS relating to the foo instances.

What you may be missing is that methods on the helper (nsIXPCScriptable in the xpconnect case) *all* receive a pointer to the instance wrapper when called. I.e. the helper methods have an explicit 'self' param. This allows the helper to customize its response for each method call without requiring a helper per instance.

See bug 67699 for more interesting discussion about pluggable language helpers and decoupling them from specific wrapped classes.

That sounds useful. How do I make it work with my code?

Why, yes, it is useful. To provide nsIClassInfo data for your class, you need to ensure that it returns an nsIClassInfo -implementing object when it is QueryInterfaced for nsIClassInfo . Simple enough, but it can be even simpler through the use of a handful of helper macros/functions. Choose your own adventure:

I'm writing C++ code, and I use the generic factory and nsModuleComponentInfo.

First, make sure that your class has the nsIClassInfo helpers, by changing the NS_IMPL_ISUPPORTS line:

NS_IMPL_ISUPPORTS2(nsSampleImpl, nsISample, nsIOther)

becomes

NS_IMPL_ISUPPORTS2_CI(nsSampleImpl, nsISample, nsIOther)

This will provide an implementation of a helper function, named nsSampleImpl_GetInterfacesHelper, which handles the processing of nsIClassInfo::getInterfaces.

Next, in your module code, use NS_DECL_CLASSINFO to provide the rest of the per-class infrastructure (a global pointer into which to stash the nsIClassInfo object, and an extern declaration of the interfaces-helper, in case it's defined in another file):

NS_DECL_CLASSINFO(nsSampleImpl)

You'll need one of these lines for each nsIClassInfo -supporting class represented in your nsModuleComponentInfo array.

Lastly, fill in the appropriate members of nsModuleComponentInfo to wire everything up:

static nsModuleComponentInfo components[] =
{
  {
    "Sample Component", NS_SAMPLE_CID, NS_SAMPLE_CONTRACTID,           
    nsSampleImplConstructor,

    nsSampleRegistrationProc,
    nsSampleUnregistrationProc,
    nsnull /* no factory destructor */,
    NS_CI_INTERFACE_GETTER_NAME(nsSampleImpl),  /* interface getter */
    nsnull /* no language helper */,
    &NS_CLASSINFO_NAME(nsSampleImpl),  /* global class-info pointer */
    0 /* no class flags */
  }

};

If you want to add a callback which provides language-helper objects, replace the last nsnull with your callback. See the nsIClassInfo IDL file for details on language helpers and other esoterica.

Note: the details of these macros may change slightly over the next week or so, as we refine a system for using table-driven QI and sharing data between QI and the class-info calls.

I'm writing C++ code, and I use a custom factory or a singleton service object.

You need some utility macros, don't ya?  We're working on it.  (You should really use the generic factory and module, though.  See nsUConvModule.cpp for an example of how to use nsModuleComponentInfo and the generic modules even when you have highly-custom registration requirements.)

I'm writing JS code.

You poor thing. You suffer without even a GenericModule helper. We're working on that, too.

What's "interface flattening"?

Interface flattening is a way to present multiple interfaces of the same object to a language binding, without requiring explicit QueryInterface calls.  Consider the following interfaces and an object obj that implements both of them:

interface nsIFoo : nsISupports {
    void fooMeth(in string message);
};

interface nsIBar : nsISupports {
    void barMeth(in PRUint32 meaning);
};

You could use the following code to call both fooMeth and barMeth without any QueryInterface:

obj.fooMeth("welcome to foo");
obj.barMeth(42);

Pretty, no?  Pretty, yes.

There are also intricacies related to conflicting method names and the difference between interface sets that are part of a contract's promise and those which are simply artifacts of the implementation, but they're beyond the scope of this overview.