Interfacing with XPCOM
John Reimer
terminal.node at gmail.com
Sun Nov 30 15:48:47 PST 2008
As the title suggests, this post is about D interfaces, specifically as supported
in D 1.0.
Here's some background:
I've been working on porting the SWT Browser package to DWT (on Linux) over
the last few months. I'm happy to say that I've mostly succeeded despite
some rather annoying problems. The important issues have been ironed out.
What remains now is to get down to some heavy debugging sessions.
SWT implements its Browser code based on the mozilla XPCOM interface. What
this means is that in order to use the SWT Browser in your project, you need
to install a mozilla package called XULRunner that provides all the components
that make up a working browser. XULRunner is basically the Mozilla Team's
way of separating the internals of Mozilla Firefox into a shared package
that serves any number of applications that want to embed browser components.
As of Firefox 3.0, the Mozilla Foundation has moved to this XPCOM component
server system; it seems that they now include the XULRunner package within
the Firefox installation instead of making it an integrated component of
the browser.
Obviously this scheme is useful for all sorts of apps that want to embed
a comprehensive browser. The only requirement is that development language
know how to interface to the Mozilla components. So the key here is that
a language must be compatible with XPCOM.
This is no problem for D on windows machines because D provides a mechanism
of direct COM interface support on that platform. XPCOM happens to be binary
compatible with the vtable layout of the COM structure, so, on windows, all
one has to do is alias the XPCOM's base interface nsISupports to the COM
IUknown. The D compiler takes care of the rest since it automatically adjusts
the vtable when it sees a reference to the IUnknown COM type.
But how does one do this on Linux? Can we use D to interface with XPCOM
there? The problem is that this will not work on Linux since COM has no
meaningful implementation on the platform primarily because all COM components
are declared extern(Windows) internally by the D compiler. This would appear
to mean that Linux is left out in the cold when it comes to interfacing with
anything like XPCOM. D interfaces obviously won't work because they are
constitute a new type, independent of any relationship to any other language
interface (D intefaces insert a vtable entry at index 0 that references RTTI).
There is, in fact, a solution for Linux:
We can use the COM interface alias technique just as we would on windows.
[code]
alias IUknown nsISupports;
[/code]
Even on linux, the D compiler will create a COM clean vtable with windows
calling convention for the methods. You don't actually notice that the compiler
has done this until you try to implement some classes based on the interface,
at which point the compiler protests loudly. Now on linux, we need to get
rid of the windows calling convention in order to make the interfaces XPCOM
compatible. The best way to do this is to directly override the compiler
by declaring each interface method extern(C); this is something XPCOM wants
to see.
[code]
interface IUnknown
{
static const char[] IID_STR = NS_ISUPPORTS_IID_STR;
static const nsIID IID = NS_ISUPPORTS_IID;
extern(System):
nsresult QueryInterface( nsIID* uuid, void **result);
nsrefcnt AddRef();
nsrefcnt Release();
}
[/code]
I've used extern(System) to demonstrate a quick way to do this for the particular
platform so the code is portable. Now we have a simple COM interface with
extern(C) calling convention that will interface acceptably with Linux XPCOM.
There's still a problem here, however. Since we are using IUnknown, the
D compiler is going to secretly force all /class/ methods that implement
the contract methods to also use extern(Windows). So within any class that
implements the interface, we also have to apply the extern(System) decoration:
[code]
class A : nsISupports {
extern(System) nsresult QueryInterface( nsIID* uuid, void **result) {}
...
}
[/code]
But what about the class methods that don't implement the interface? Well,
they have to be explicitly declared extern(D) in order to keep the compiler
from complaining again.
[code]
class A : nsISupports {
extern(System) nsresult QueryInterface( nsIID* uuid, void **result) {}
...
extern(D) void handleFolderEvent( Event event );
}
[/code]
The above code worked in porting the Browser code to DWT specifically for
D version 1.0. But I have to admit that I remain a little uncomfortable
implementing the fix this way. It's a hack. Unfortunately, it's the only
way I know that simply and effectively solves this problem.
Here are a couple of thoughts I have:
(1) I'm wondering if the implicit COM support (triggered by the IUnknown
symbol only) implemented in D is such a good idea. Would such a feature
be more correctly supported as a Pragma wherever an interface vtable type
is augmented in a system specific way? pragma("COM") {} or something to
that effect.
(2) As an alternate solution to my trick with COM on linux, there is also
the option of using extern(C++), except that it only exists in D 2.0. I
think using extern(C++) might work in this situation because mozilla XPCOM
interfaces are often represented in C++ using single inheritance C++ class
structures. The vtables should match unless I'm missing some important detail.
Porting extern(C++) to D 1.0 should not constitute a spec change, and it
could mean simpler support for Mozilla XPCOM without ugly workarounds. Does
this sound reasonable?
For more information, I've also covered the same material here:
http://www.dsource.org/projects/dwt/wiki/PortingJournal
Here's a link to the Walter's discription of D 2.0 Interfaces and the addition
of extern(C++) along with its caveats:
http://www.digitalmars.com/d/2.0/cpp_interface.html
Notice that in the above link Walter says COM only works on windows. ;)
-JJR
More information about the Digitalmars-d
mailing list