A zero generated code XPConnect proposal
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:
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.
- 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.
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 troubleThere 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 interfacesWe 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