Frequently Asked Questions about XPConnect and XPIDL

Table of Contents

1) My component isn't scriptable from js! What's going on?
Add the [scriptable] attribute.
mccabe - 7/17/99
2) I need to define a lot of constants in my interface. Why can't I use enum instead?
See discussion in bug 8781 and .xpcom.

here is some more text on the topic...

'enum' is real sticky. While powerful in C++ as sets of valid
values and a 'types' for params, they are a mess in typelibs and,
I think, should not be used as types for params in xpcom. A core
problem is that the sizeof a given enum is something that the C++
compiler can decide based on the range of values for the enum.
This makes them hard to specify in the typelib or to use safely
in xpconnect. I say that they are evil as part of the contract
between xpcom components.

Another issue is that corba idl (upon which the xpidl compiler is
built) requires named enum (no anonymous enums) and does *not*
allow setting individual values; e.g. 'enum foo{a,b};' is valid
but 'enum {a,b};' and 'enum foo {a=-1,b=50};' are not valid.

xpidl *had* supported writing enums into the generated C++
headers, but not into typelibs. I removed that support and added
stubs to the compiler that will do a -w warning if enums are used
to let users know that they are being ignored.

Instead of using enums for constant numerical identifiers in
xpidl I propose that we use 'const's. shaver *had* support for
many types of consts when generating typelibs (based on the
typelib spec) and no support (yet) when generating headers. I
decided that the *only* types of consts we need are those that
can be supported in both headers and typelibs and that really
important in the real world: integer consts.

String consts are not supported - they are a pain to do safely in
C++ without generating C++ implementation files, and would force
linkage across modules (or duplication of strings). Any interface
that wants the effect of string consts can declare string
attributes and have some implementation of the interface return
the strings. Yes, this does not declare the values in the
interface declaration, but I argue that this is *good enough*.

Floating point constants are messy in C++, not as common, and are
not supported here.

Integer constants are much more important and are supportable as
part of the interface. I decided to implement support only for
shorts and longs (16 and 32 bit integers) signed and unsigned.
These are only supported *inside* interfaces (xpidl will -w warn
when consts are seen outside interfaces). They look like:

 const short some_name = -1;

When generating the C++ header this generates:
 enum { some_name = -1 };

So, constants are always available in C++ as
iface_name::const_name. They are written into the typelib and are
available in JavaScript via xpconnect as named properties on any
instance of the given interface.

xpidl supports some arithmetic in const declarations. This is
resolved *before* emitting C++ headers. This includes things
like:

 const short c1 = 1+1;
 const short c2 = c1 * 5;
 const short flag = 1 << 5;

which produces:

 enum { c1 = 2 };
 enum { c2 = 10 };
 enum { flag = 32 }; 

jband - 7/17/99
3) How do I use in/out parameters in JavaScript?
inout params are reflected into JS, but the syntax is different from the syntax
used for either in or out params. For out  params xpconnect remaps the C++
result to be the return value when called by JS.  This is as expected. But JS
has no smooth intrinsic way to do inout because JS has no pointers. So for inout
params the rule is that JS callers must supply an object with a 'value'
property. The caller can set that 'value' property to the 'in' value before the
call and extract the 'out' param from the 'value' property of that object after
the function returns.

I have added a test of this. I'll show parts of it here for illustration.

- testxpc.idl gets a new method:
    void SetReceiverReturnOldReceiver(inout nsIEcho aReceiver);

- TestXPC.cpp implements it:
/* void SetReceiverReturnOldReceiver (inout nsIEcho aReceiver); */
NS_IMETHODIMP
MyEcho::SetReceiverReturnOldReceiver(nsIEcho **aReceiver)
{
    if(!aReceiver)
        return NS_ERROR_NULL_POINTER;

    nsIEcho* oldReceiver = mReceiver;
    mReceiver = *aReceiver;
    if(mReceiver)
        NS_ADDREF(mReceiver);

    /* don't release the reference, that is the caller's problem */
    *aReceiver = oldReceiver;
    return NS_OK;
}

- testxpc.js uses it and verifies that the results are correct:
var e1 = new Object();
var e2 = new Object();

var v1 = new Object();
var v2 = new Object();
var v3 = new Object();
var v4 = new Object();

echo.SetReceiver(null);
all_ok = true;

v1.value = e1;
echo.SetReceiverReturnOldReceiver(v1);
all_ok = all_ok && v1.value == null;

v2.value = e2;
echo.SetReceiverReturnOldReceiver(v2);
all_ok = all_ok && v2.value == e1;

v3.value = null;
echo.SetReceiverReturnOldReceiver(v3);
all_ok = all_ok && v3.value == e2;

v4.value = e1;
echo.SetReceiverReturnOldReceiver(v4);
all_ok = all_ok && v4.value == null;

print("inout of interface tests - "+(all_ok ? "passed" : "failed"));
4) My component builds but the interface doesn't show up through "Components.interfaces.Foo"
Make sure that you have the magic invocations in your makefile. See the makefiles in mozilla/xpcom/sample/ for examples.
mang - 8/03/99
5) Where can I find some documentation on IDL?
XPIDL docs are at http://www.mozilla.org/scriptable/xpidl The xpidl syntax stuff here is out of date. Use this tracking bug to add comments or questions. There is simple types and keywords list that jband wrote as a basis for documentation. Hopefully this will be superceeded with better docs soon.
mang - 8/03/99
6) What's different between XPIDL and other IDLs?
XPIDL is based on Corba IDL. It is a simple subset of Corba IDL with some xpcom extensions added. Use this tracking bug to enter your flames.
mang - 8/03/99
7) Does xpidl support string constants?
String consts are not supported - they are a pain to do safely in C++ without generating C++ implementation files, and would force linkage across modules (or duplication of strings). Any interface that wants the effect of string consts can declare string attributes and have some implementation of the interface return the strings. Yes, this does not declare the values in the interface declaration, but I argue that this is good enough.
jband - 7/28/99
8) How do I make 'out string' work right? Why does my implementation of this crash when called from JavaScript?
XPConnect requires that objects follow the xpcom rules of transfer of ownership of pointer type 'out' params. I don't know that we have any definitive docs on this, but this has been hashed out in some detail on the mozilla xpcom newsgroup more than once. I'll try to explain...

The critical point is that when returning a pointer type (char*, PRUnichar*, nsID*) from an xpcom call you need to use the nsIAllocator service to allocate a copy of the thing and transfer ownership of the copy to the caller. This is completely analogous to the AddRef rules when passing nsISupports* as 'out' or 'inout' params.

All callers to a compliant interface should be able to safely assume that you did follow the transfer of ownership rule and thus they accept responsibility to free the copy when they are done with it.

The common mistake is to do something like:

/* XXX incorrect implementation! */

/* string GetStringA (); */
NS_IMETHODIMP
xpcstringtest::GetStringA(char **_retval)
{
    *_retval = "result of xpcstringtest::GetStringA";
    return NS_OK;
}        
This code is passing a pointer to static memory. However the caller expects an allocated block which it is expected to later free. So when free is called the heap manager complains.

While in this case xpconnect is the module tripping over this bit of non-compliance in the implementation, it could just as well be any other caller.

I just added some test code to xpconnect/idl and xpconnect/tests/components to demostrate and to use for tests.

given:

[scriptable, uuid(d970e910-30d8-11d3-9885-006008962422)]
interface nsIXPCTestString : nsISupports {
    string GetStringA();
    void GetStringB(out string s);
};
we get the header output:
class nsIXPCTestString : public nsISupports {
 public:
  NS_DEFINE_STATIC_IID_ACCESSOR(NS_IXPCTESTSTRING_IID)

  /* string GetStringA (); */
  NS_IMETHOD GetStringA(char **_retval) = 0;

  /* void GetStringB (out string s); */
  NS_IMETHOD GetStringB(char **s) = 0;
};
When your native implemenation returns pointer types it needs to use the shared allocator to make a copy. You can get the nsIAllocator service from the service manager to do this, or use the static nsAllocator class. Here is an example from xpconnect/tests/components/xpctests_string.cpp:
/* string GetStringA (); */
NS_IMETHODIMP
xpcstringtest::GetStringA(char **_retval)
{
    const char myResult[] = "result of xpcstringtest::GetStringA";

    if(!_retval)
        return NS_ERROR_NULL_POINTER;

    *_retval = (char*) nsAllocator::Clone(myResult, 
                                          sizeof(char)*(strlen(myResult)+1));
    return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}        
Yes, this is a pain. But, it is the way to have consistent rules about thransfer of ownership. It looks overblown in this case where we have a static string that is being returned. But the same interface could be returning a newly allocated string on every call. By having a consistent convention and sticking to it we can avoid errors. It is the sticking to it part that is hard.

The shared allocator can be found by asking the ServiceManager for the service with the progid: "component://netscape/allocator" and interface: nsIAllocator. The simpler method is to use the 'nsAllocator' object that exposes all static methods see http://lxr.mozilla.org/mozilla/source/xpcom/base/nsIAllocator.h. Also, Scott Collins has suggested that he might map C++ new and delete for mozilla to use the shared allocator to simplify this for all.

For the *very* rare cases where you absolutely need to break the xpcom rules and not do a transfer ownership, xpidl provides the [shared] keyword. This will let xpconnect know that you are breaking the rules and not transfering ownership. But this should never be used unless *every* possible caller also know that this method breaks the rules. C++ has no standard way to convey this between components. So if your component were used by some random other component in the future that other component would have to know be some special means that this method is *special*. Any 'generic' user of your method would crash just like xpconnect did here. I strongly discourage the use of 'shared' in any public interfaces. It is just a way of asking for future crashes.
jband - 7/30/99

9) I made my component scriptable and wrote some JavaScript code to test it. So, how do I run my tests?
Start small and work up.

If you have a js file called a.js then you can write an html file called a.html that looks like:

<html>
<script src="a.js"></script>
</html>
Start apprunner and load the following url (assuming that the html file is in c:\temp):

file:///c|/temp/a.html

This should load a.js and run it. This works fine if you only want to dump stuff to the console to test your code.

For instance I have a.js with the contents:

try {
    var clazz = Components.classes["nsEcho"];
    var iface = Components.interfaces.nsIEcho;

    dump("clazz: "+clazz+"\n");
    dump("iface: "+iface+"\n");

    var supports = clazz.createInstance();
    dump("supports: "+supports+"\n");

    var echo = supports.QueryInterface(iface);
    dump("echo: "+echo+"\n");

    var result = echo.In2OutOneString("foo");
    dump("call returned: "+result+"\n");

} catch(e)  {
    dump("caught exception: "+e+"\n");
}
When I load file:///c|/temp/a.html it prints to the console:

Going to reload
FROM:chrome://navigator/skin/animthrob.gif
TO:resource:/chrome/navigator/skin/default/animthrob.gif

clazz: nsEcho
iface: nsIEcho
supports: [xpconnect wrapped nsISupports]
echo: [xpconnect wrapped nsIEcho]
call returned: foo
Document file:///c|/temp/a.html loaded successfully
Document: Done (0.271 secs)
Note: if there is an error in your *.js file then apprunner will report that the error was in the *.html file that loaded the js file. This is a known bug on vidur's list.
jband - 7/31/99

Last Modified: Fri Aug 27 17:29:06 GMT-0700 (PDT) 1999