|
A zero generated code XPConnect proposal
John Bandhauer
- 3 Dec 1998
Introduction
XPConnect is an evolving technology which enables JavaScript code to call
across XPCOM interfaces into C++ objects and also C++ code to call across XPCOM
interfaces into JavaScript objects.
See here for further details about XPConnect
This proposal is presented as an alternative to my (old and hastily written!)
zero-ASM proposal. This proposal presents a
plan for implementing XPConnect without any per interface generated code. It
also suggests putting InterfaceInfo information (generated by the xpidl
compiler) into a non-compiled XP file format. This is a minimal footprint
solution at the cost of requiring some platform specific assembly code for
marshalling params and dispatching calls from C++ to JS and from JS to C++.
InterfaceInfo files and objects
XPIDL, the IDL compiler, will generate header
files for inclusion in C++ code, interface documentation files, and - for
XPConnect - InterfaceInfo files. InterfaceInfo files will be non-platform
specific files which contain all pertinent information about the XPCOM interface
described in the IDL description expressed in a format which can be efficiently
accessed at runtime. Each InterfaceInfo file will contain information about one
or more interfaces. We will likely build tools to merge multiple InterfaceInfo
files together. InterfaceInfo files will be comparable to MS typelib files.
Tools to convert one to the other may be written. I do not see it as necessary
to try to be otherwise compatible with Microsoft's typelib implementation.
InterfaceInfo files will consist of sets of tables describing interfaces, their
methods, and the methods' parameters. The tables will be indexed for quick
access. A main table will link UUIDs to the offsets in the file of the
information about a given UUID's interface. A constant pool will be used to
minimize overlap of shared data. Appropriate versioning information will be
stored.
InterfaceInfo files will be platform independent. A factory class will be
implemented which when given a UUID can return a pointer to an nsIInterfaceInfo
object. the factory and the nsIInterfaceInfo objects will hide all of the file
oriented details.
nsIInterfaceInfo objects will contain all the information needed at runtime to
dynamically build XPConnect glue and allow for introspection of the interface
hierarchy. nsIInterfaceInfo objects will be reference counted and shared. There
need be only one implementation; i.e. the same C++ class will be instantiated to
represent various sorts of InterfaceInfo types. A linked hierarchy representing
the interface inheritance schema of the target interfaces will be reflected in
the instantiation of these nsIInterfaceInfo objects; i.e. if nsIFoo inherits
from nsISupports, then when asked for an nsIInterfaceInfo object for nsIFoo the
factory will return one to represent nsIFoo and that objects will have setup a
link to another nsIInterfaceInfo object representing nsISupports.
The factory object that provides nsIInterfaceInfo may have access to any number
of InterfaceInfo files from which it builds the requested nsIInterfaceInfo
objects.
Proxies
XPConnect glue will work by instantiating proxies (aka wrappers). JavaScript
code accesses C++ XPCOM objects by calling through a JS2CPPProxy object which
exposes a JavaScript native object interface and forwards calls to the wrapped
C++ XPCOM object.The proxy is responsible for converting parameters and return
values and doing appropriate error checking. A CPP2JSProxy object supports C++
calling JavaScript objects. A primary goal is that objects on either side of the
interface need not know the implementation language of the object on the other
side of the interface; The C++ code makes normal XPCOM method calls and the
JavaScript code interacts with the proxy like it would with any other JavaScript
object.
These two classes of proxies will be able to be instantiated to support any
XPCOM interface. They use nsIInterfaceInfo objects to configure themselves to
appropriately wrap objects and forward calls. They handle the details of
supporting the nsISupports QueryInterface and reference counting transparently.
These proxies are automatically used to wrap nsISupports derived object pointers
as they are passed as parameters or return values.
NOTE: I'm not real excited about the names JS2CPPProxy and CPP2JSProxy.
JS2CPPProxy details
The guts of this proposal are the the implementation details of the two kinds of
proxy. JS2CPPProxy will wrap C++ XPCOM objects to make them accessible from
JavaScript. It will use information in an associated InterfaceInfo object to
construct a JavaScript native interface to present JavaScript code with mapped
methods, property getters/setters, and interface constants.
The proxy is a garbage collected JavaScript native object. It will hold a
reference to the wrapped XPCOM object which it will release upon finalization.
Proxies will maintain links to other proxies on the same object so that
'redundant' QueryInterface calls from the JavaScript code result in sharing of
proxies.
Getters, setters, and method stubs will forward their params (either as explicit
jsvals or as argc/argv for methods) to a method which will use the the
proxy's associated InterfaceInfo object to determine how to convert and
validate the params. The output of this conversion could be one of two forms:
- If we decide to support IDispatch then the conversion will be to an array of
variants which is then passed to a shared invoke method which uses some platform
specific assembly code to marshal arguments, call the target method, check for
success, and convert the results. This scheme partially decouples the JavaScript
native object stub stuff from the platform specific invoker, but has a bit of
extra overhead in building the intermediate array of variants.
- Alternately, we could skip the IDispatch part and do data conversions
one by one directly into the space used for marshalling params for the method
call. This is somewhat simpler.
Here we see the big cost of this whole approach - we would need to port the
assembly code for marshalling params and calling C++ objects to each platform we
choose to support. This is not, however, a great deal of code.
CPP2JSProxy details
CPP2JSProxy objects will handle wrapping JavaScript objects to be called from
C++. Others have focused little attention on this part arguing that this is
not important or rare enough to be codable by hand. I think it is very important
and will allow JavaScript objects to participate as first class citizens in the
XPCOM universe.
These proxies will need to simulate C++ objects. They need vtbls of
method pointers that masquerade as a C++ implementation of the interface they
represent. Rather than trying to synthesize these vtbls and stub
methods on a per interface basis, we can use a shared vtbl and set of stubs for
all instances of CPP2JSProxy. This vtbl will be long enough to accomodate the
longest anticipated interface (and a bit longer) - say 500 or 1000 entries with
some entries at the end being sentinels with 'assert'ing stubs to detect
overruns. Each stub will push a number indicating which method (slot in the
vtbl) it represents and then forward the call to a prolog/epilogue free method
which will then do the work of extracting the params (based on information in
the InterfaceInfo object associated with the proxy instance) for the given
method, converting the params, finding and calling the appropriate JavaScript
method (or getter/setter), and doing the right thing to return results. It will
fail gracefully when necessary.
The implementation of the stubs and the shared param extraction code will be
quite platform specific. On some platforms this can be mostly in C++ with a bit
of inline assembly. Other platforms may require more of it to be written in
assembly.
Finding the method/param information necessary to dispatch a given call will
sometimes require walking the chain of InterfaceInfo objects. The InterfaceInfo
object should transparently return method info for any given method 'slot' even
if that method is part of a superinterface.
Finding and instantiating proxies
There needs to be a bridge for using the factory registry from JavaScript. The
JavaScript code needs to be able to both get at existing factories and register
factories of its own. The XPConnect runtime should also expose functions by
which the application could register application level objects for access from
JavaScript.
Whenever interface pointers are passes across the XPConnect boundary (in either
direction) they will be automatically wrapped by proxies. This may require some
additional QueryInterface invocations for determining identity with 'known'
wrapped objects and also some tables to map known wrapped objects to their
wrappers. There are tradeoffs between unfailingly reusing existing wrappers and
sometimes synthesizing redundant wrappers. This needs to be further worked
out and may be resolved with the benefit of empirical evidence. Regardless, it
is critical that the COM identity rules be followed.
Additional Issues
It is not clear (to me) if supporting an IDispatch interface is worth the
trouble
There are potential cycle problems with reference chains that bridge the gap
between the reference counted side and the garbage collected side more than
once.
What sort of registry is going to be available? Will JavaScript programmers need
to work in terms UUIDs, or will there be a reliable string to UUID registry that
we can expose into JavaScript?
It is not clear if InterfaceInfo objects should be reference counted and
disposed of immediately when the count goes to zero, allowed to accumulate
unchecked (though always reused), or 'occasionally' be collect if they have zero
reference counts. Since they are presumably relatively expensive to build from
file, we would hate to see patterns where the 'same' object is repeatedly built
and destroyed.
TBD add more issues
Tradeoffs
The big tradeoff question is: should we use this scheme that gives up no per
interface code but requires porting to each platform, or a different scheme that
has per interface code and no porting. (I vote for this scheme)
Potential enhancements
We could expose introspection of interfaces
We could support MSCOM compatible IDispatch (even IDispatchEx?)
We could build InterfaceInfo inspection tools
The IDL compiler (or some tool using the InterfaceInfo files) could generate
JavaScript skeleton and sample code to demonstrate how to either implement or
access a given interface from JavaScript.
TBD add more enhancements
|