Back to Basics: Judo with nsIVariant

With XPIDL, XPCOM components can be very strict about what they allow.
Simply put, if you try to pass in an argument to a XPCOM component, and the
component requires a type that your argument doesn’t support, it won’t work.
In C++, your program won’t compile; in JavaScript, XPConnect throws
exceptions. This is a good thing.

Many of the basic data structures – nsIArray, nsIPropertyBag, etc. – take
a nsISupports argument. This covers most objects you construct
and work with. This is all well and good… unless you want to pass in a raw
type such as a number, a true or false value, a
string, etc. Then you’re stuck.

Fortunately, all is not lost. There is an interface named
nsIWritableVariant, which XPCOM provides for wrapping native
types in an nsISupports object. (There are type-specific
interfaces like nsISupportsPRBool as well, but
nsIWritableVariant is an one-size-fits-all solution.) Even more
interesting, its read-only companion, nsIVariant, is “magical”
to XPConnect: JavaScript receives its values as native types, not as
nsIVariant objects.

Thus, nsIVariant turns the strengths of XPIDL barriers so
that they are no longer fighting you, but working with you. This is what I am
calling judo with nsIVariant. Read on in the extended entry for more details.

Creating a variant

In C++, there’s a number of methods for setting types. If you have a
PRUint8, you can call setAsPRUint8(val), for
example. The rest of the methods are pretty self-explanatory, but two methods
are also interesting: setAsISupports() and
setAsInterface(). These two allow you to wrap any
nsISupports object in a variant.

In JavaScript, almost all the methods are available, but there’s a nice
shortcut: the setAsVariant() method. The beauty of this is that
you can pass in any JavaScript value to this – XPConnect converts
the argument into a nsIVariant for you. This has special,
“magical” implications.

Retrieving a variant

In C++, you have to work a little bit. First, you have to get the data
type (call GetDataType() on the variant). This will tell you
which (if any) methods of nsIVariant to call.

In JavaScript, it’s even easier. If the interface you’re accessing
specifies the argument is a nsIVariant, then you don’t have to
do anything – XPConnect will convert it for you. This is more of the
“magical” nature of these variants. If the interface specifies
nsISupports, then you’ll get back a nsISupports
object which implements nsIVariant. You can get the native value
by querying the object for nsIVariant, as shown below:

js> var variant = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant);
js> variant.setFromVariant(2)
js> variant
[xpconnect wrapped nsIWritableVariant @ 0xe27280 (native @ 0xe0e6c8)]
js> variant.QueryInterface(Components.interfaces.nsIVariant)
2
js> typeof variant
object
js> typeof variant.QueryInterface(Components.interfaces.nsIVariant)
number

It’s worth noting that, technically, XPConnect is violating the rules of
nsISupports here. The QueryInterface() method
should technically always return an object that you can call
QueryInterface() on again. This is not the case for
nsIVariant:

js> variant.QueryInterface(Components.interfaces.nsIVariant).QueryInterface(Components.interfaces.nsISupports)
typein:8: TypeError: variant.QueryInterface(Components.interfaces.nsIVariant).QueryInterface is not a function

On the other hand, it is consistent with JavaScript object
identities:

js> var variant = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant);
js> var func = function() { dump("Hello World!\n"); }
js> variant.setFromVariant(func)
js> variant
[xpconnect wrapped nsIWritableVariant @ 0xe27280 (native @ 0xe0e6c8)]
js> variant.QueryInterface(Components.interfaces.nsIVariant)
function () {
dump("Hello World!\n");
}
js> variant.QueryInterface(Components.interfaces.nsIVariant)()
Hello World!
js> variant.QueryInterface(Components.interfaces.nsIVariant) == func
true
js> variant.QueryInterface(Components.interfaces.nsIVariant) === func
true

I had thought about introducing a number of nsIVariant-based
interfaces to parallel the basic data structures, but while working on this
article, I realized… why? The whole point of
nsIVariant is to wrap changeable values in a
nsISupports structure. Since these data structures work with
nsISupports naturally, it doesn’t make any sense to add an extra
layer of abstraction.

One thought on “Back to Basics: Judo with nsIVariant”

  1. With a little XPCOM trickery involving Components.Constructor, you can make this even simpler to use:
    var Variant = Components.Constructor(“@mozilla.org/variant;1”, “nsIWritableVariant”, “setFromVariant”);
    var v = new Variant(5);

Comments are closed.