XPCORBA is made up of two almost independent parts, for mapping calls from one direction to the other.
- A bridge which implements the CORBA interface, and maps calls received through CORBA onto an associated XPCOM object. Sources belonging to this part mostly start with corba.
- A bridge which implements the XPCOM interface, and maps calls received through XPCOM onto an associated CORBA object. Sources belonging to this part mostly start with xpcom.
- In values are owned by the caller, but the callee is allowed to use the value for the duration of the call.
- Out values and return values are initially owned by the callee, but ownership passes to the caller after the call returns.
- Ownership of the initial value of an in/out parameter passes from the caller to the callee. The output value then passes from the callee to the caller after the completion of the call. If the value is never touched, ownership will pass from the caller to the callee, and then back to the caller.
- Where a callee is allowed to use an object reference (i.e. this doesn't apply to strings etc...), the callee may take ownership by calling add_ref.
- Ownership cannot simply be abandoned, the owner must give up ownership by calling release_ref (object reference) or by deleting the pointer (strings etc...).
All objects support the following operations, by virtue of their inheritance from the base object, XPCOM::IObject.
- add_ref. This two-way call increments the reference count. Note that this is not needed after gaining ownership of an object through another means (e.g. return value).
- release_ref. This is a one-way call, which decrements the reference count and gives up ownership. The object could be deleted at any time after the reference count falls to zero, so the object must not be used after ownership is given up by release_ref.
- objid. This returns a globally unique object identifier. This must be identical for two objects if and only if the objects are identical. Putting a UUID here is a good way to ensure this, but care should be taken that it is correctly passed through all bridges (especially where bridges use short-lived temporary wrapping objects, where two different wrapping objects may wrap the same real object).
- query_interface. This takes an interface. If the interface is supported, this returns an XPCOM::IObject object suitable for being cast into an instance of the requested object. However, bridges must take special care that all wrappers are suitable to allow the cast to take place at the endpoint.
A standard portable object adaptor is created and wrapped by the CORBA_POA class. There should only be one of these objects per process.
The implementation uses a custom servant manager, called CORBA_Proxy_Locator (defined in corba_locator.h). There is only one CORBA_Proxy_Locator object per process. This is used to find the correct servant to use for each call coming from CORBA. It is the proxy locator which keeps the registry of all objects in the system.
The POA wrapper has several public methods. register_object is used to create the initial CORBA wrapper object from an XPCOM object. It will do everything required, including informing the CORBA_Proxy_Locator of the new object. attempt_unwrap_object will check to see if it is possible to extract an XPCOM object from an existing CORBA object (this will succeed if the CORBA object is a locally wrapped XPCOM object). unregister_object tells the locator to forget about an XPCOM object. reference_to_servant will ask the POA to retrieve the servant corresponding to an object reference.
The CORBA proxy locator is only accessible from the POA object and the ORB. However, it implements the CORBA servant manager interface, and uses the preinvoke hook to select the correct servant.
In XPCOM, it is possible for an object to support more than one interface, and there is no universally valid way to determine a priori what interfaces an object supports. In order to switch from one interface to another, it is neccessary to 'query interface' to the correct interface. However, generated bridges can only support one interface (or one hierarchy of interfaces related by inheritance) at a time.
To get around this, XPCORBA has two types of objects, CORBA_Proxy_Objects (corba_proxy.h) and CORBA_Proxy_Servants (corba_servant.h). Confusingly, both are actually CORBA servants. There is exactly one CORBA_Proxy_Object per real object (note: this is actually sometimes untrue, because we create the wrappers on demand, and we don't bother checking for existing wrappers before creating a new one. However, this is irrelevant to the discussion here, which is about how we support multiple interfaces), and exactly one CORBA_Proxy_Servant per interface ever used on the CORBA_Proxy_Object. This means that there is a one-to-many relationship between CORBA_Proxy_Objects and CORBA_Proxy_Servants.
All servants for a given object are stored in the CORBA_Proxy_Object datastructure, as well as a table of the correct servant for a given operation name. Servants are created by calling add_repoid on CORBA_Proxy_Object. This results in a call to the CORBA_Proxy_Factory (see below) to create a new proxy object supporting that interface, and registration in the datastructures. Servant lifecycles are tied to the object lifecycles, and are destroyed in the destructor for the CORBA_Proxy_Object.
The base CORBA_Proxy_Servant implementation is in corba_servant.cc. However, unlike CORBA_Proxy_Objects, servants must be generated from each IDL source, in order to contain the correct forwarding code. In order to locate such a servant, all servants have a corresponding factory class. The factory class gets constructed as a static object. All such factories extends from CORBA_Proxy_Factory (corba_proxy.cc), and the constructor for this base registers the factory against the repoid. This system means that, given a repoid, it is possible to add support for an interface into an object.
Note: CORBA_Proxy_Object has two reference counts. One of them, referred to as 'the XPCOM refcount', is incremented and decremented through the calls add_ref and release_ref on the XPCOM::IObject interface. This reference count can be externally manipulated. Once the XPCOM refcount hits zero, no-one external should be touching it, although the ORB may still make use of the object. The other reference count, the 'CORBA refcount' is the standard servant refcount, and is incremented and decremented through _add_ref and _remove_ref. This reference count is only manipulated by the ORB and the locator. Because the locator maintains a CORBA reference from creation time until the XPCOM refcount falls to zero, a non-zero XPCOM refcount implies a non-zero CORBA refcount. However, a non-zero CORBA refcount does not imply a non-zero XPCOM refcount.
- Object gets created by xpcom2corba (e.g. a return value) or through a similar mechanism. Reference count on the wrapper starts at one. The wrapper holds a reference to the XPCOM object, because the proxy has an nsCOMPtr to the nsISupports sub-object.
- Attempts to call on the object result in _is_a calls, which result in the required CORBA_Proxy_Servant implementations being added to the object.
- When the caller is finished with the CORBA_Proxy_Object, it calls release_ref(). This causes the XPCOM reference count to fall to zero. The object is deregistered, and this causes the locator to drop its CORBA reference. When the ORB has finished with the servant, the CORBA refcount falls to zero, and the CORBA_Proxy_Object is deleted.
- The CORBA_Proxy_Object destructor causes all the CORBA_Proxy_Servants to have _remove_ref called. When the ORB finishes with them, their refcount falls to zero and they too are deleted.