Implementing a C++ - UNO bridge |
Contents
Objective
Terminology
Loading a bridge
Environment
Mapping
Microsoft Visual C++ - UNO bridge
Objective
This document is written for developers implementing a bridge from C++ to binary UNO. The last paragraph covers the Microsoft Visual C++ - UNO bridge in detail.
Terminology
- Environment
An environment is specific to a programming language/ the compiler in which interfaces are implemented, e.g. the name "msci" for a C++ environment using the Microsoft Visual C++ compiler. It can also be session specific for some reasons, e.g. when running multiple JVM (Java virtual machines) in one process.
- Mapping
A mapping is the directed way to publish an interface into another environment, i.e. you map an interface from a source environment to a target environment. So you can invoke methods on a mapped interface (relating to the target environment) which are delegated to the originating interface in the source environment. The delegation/ invocation is performed by the bridge. A mapped interface is called a proxy.
- Bridge
A bridge denotes the infrastructure to exchange interfaces between two environments and is bidirectional, supporting a mapping for each direction.
Example: The language binding Visual C++/UNO supports two mappings to exchange interfaces from the environment "Visual C++" to the UNO environment and vice versa.
Loading a bridge
Each bridge is implemented in a separate shared library loaded by the UNO runtime. The naming scheme of the library is a concatenation with the following:
[purpose_]SourceEnvName_DestEnvName
The optional purpose denotes the purpose of the bridge, e.g. protocol traffic between two environments. If no purpose is given, then the bridge just maps interfaces from source to destination environment.
Windows examples: prot_uno_uno.dll, msci_uno.dll
Solaris examples: libprot_uno_uno.so, libsunpro5_uno.so
The bridge library exports two functions called
uno_ext_getMapping()
and uno_initEnvironment()
. The
latter is currently implemented by the bridge library for pragmatic reasons.
The runtime will lookup an initialization library of an environment by loading
a library with name EnvName_uno. uno_getEnvironment()
initializes the environment, e.g. raises the JVM, sets a disposing callback
etc. C++ environments usually do nothing.
void SAL_CALL uno_initEnvironment( uno_Environment * pEnv );
The first function, uno_ext_getMapping()
, is called by the UNO
runtime to get the mappings for both directions. The
uno_ext_getMapping()
call receives a source and destination
environment handle to distinguish which mapping is demanded. It is quite clear
that the bridge library cannot be unloaded while any code of it is still
needed, i.e. interfaces are held. So both mappings and any wrapped interface
(proxy) that is exported needs to modify a shared library wide reference
count.
void SAL_CALL uno_ext_getMapping( uno_Mapping ** ppMapping, uno_Environment * pFrom, uno_Environment * pTo );
Environment
The intention of an environment (and programmatically environment handles) is to identify (given by its type name and context pointer) and optionally to provide extra functionality like interface registration.
In specific the latter point is very important, because of the object identity of an interface. Any UNO object is defined to provide the same instance of XInterface any time it is queried for it. This specification has been made to test whether two interfaces belong to the same object (e.g. when testing the source object of an incoming event). So when interfaces are mapped around to some environments outer space, they must provide the same XInterface in each environment (e.g. in C++, equal XInterface pointers).
Also it is recommended to reuse any interface you can, i.e. reducing the construction of proxy interfaces as often as you can, because each constructed proxy interface leads among the acquisition of resources to another indirection when called.
The structure of an environment is split into the simple common "identity" part which can easily be implemented by bridges that handle object identity issues in their own way. If the environment implementation supports interface registration functionality etc. then the optional pointer is set. Optional functionality is interface registration, acquiring/ releasing interfaces of the environment and obtaining object identifiers for an interface.
Interface registration is divided into registration of proxy and original interfaces. Obviously proxies have to be held unacquired, otherwise mapped interfaces will never die. The rule for a reference counted C++ proxy is, that it registers itself when reference count increments from 0 to 1 and revokes itself when the count decrements from 1 to 0. To synchronize registration access while not granting internal locks on the interface registration, the proxy will be explicitly freed by the environment by its given freeProxy() function at registration.
/** The binary specification of an UNO environment. */ typedef struct _uno_Environment { /** reserved for future use (0 if not used) */ void * pReserved; /** type name of environment */ rtl_uString * pTypeName; /** free context pointer to be used for specific classes of environments (e.g., a JVM pointer) */ void * pContext; /** pointer to extended environment (interface registration functionality), if supported */ uno_ExtEnvironment * pExtEnv; /** Acquires this environment. @param pEnv this environment */ void (SAL_CALL * acquire)( uno_Environment * pEnv ); /** Releases this environment; last release of environment will revoke the environment from runtime. @param pEnv this environment */ void (SAL_CALL * release)( uno_Environment * pEnv ); /** Call this function to explicitly dispose this environment (e.g., release all interfaces). You might want to call this function before shutting down due to a runtime error. @param pEnv this environment */ void (SAL_CALL * dispose)( uno_Environment * pEnv ); /* ===== the following part will be late initialized by a matching bridge ===== * * ===== and is NOT for public use. ===== */ /** CALLBACK Disposing callback function pointer that can be set to get signalled before the environment is destroyed. @param pEnv environment that is being disposed */ void (SAL_CALL * environmentDisposing)( uno_Environment * pEnv ); } uno_Environment; /** Generic function pointer declaration to free a proxy object if it is not needed by the environment anymore. Any proxy object must register itself on first acquire() call and revoke itself on last release() call. This can happen several times because the environment caches proxy objects until the environment explicitly frees the proxy object calling this function. @param pEnv environment @param pProxy proxy pointer */ typedef void (SAL_CALL * uno_freeProxyFunc)( uno_ExtEnvironment * pEnv, void * pProxy ); /** Generic function pointer declaration to allocate memory. Used with getRegisteredInterfaces(). @param nBytes amount of memory in bytes @return pointer to allocated memory */ typedef void * (SAL_CALL * uno_memAlloc)( sal_uInt32 nBytes ); /** The binary specification of an UNO environment supporting interface registration. */ typedef struct _uno_ExtEnvironment { /** inherits all members of an uno_Environment */ uno_Environment aBase; /** Registers an interface of this environment. @param pEnv this environment @param ppInterface inout parameter of interface to be registered @param pOId object id of interface @param pTypeDescr type description of interface */ void (SAL_CALL * registerInterface)( uno_ExtEnvironment * pEnv, void ** ppInterface, rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr ); /** Registers a proxy interface of this environment that can be reanimated and is freed explicitly by this environment. @param pEnv this environment @param ppInterface inout parameter of interface to be registered @param freeProxy function to free proxy object @param pOId object id of interface @param pTypeDescr type description of interface */ void (SAL_CALL * registerProxyInterface)( uno_ExtEnvironment * pEnv, void ** ppProxy, uno_freeProxyFunc freeProxy, rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr ); /** Revokes an interface from this environment. You have to revoke any interface that has been registered via this method. @param pEnv this environment @param pInterface interface to be revoked */ void (SAL_CALL * revokeInterface)( uno_ExtEnvironment * pEnv, void * pInterface ); /** Provides the object id of a given interface. @param ppOut inout oid @param pInterface interface of object */ void (SAL_CALL * getObjectIdentifier)( uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface ); /** Retrieves an interface identified by its object id and type from this environment. Interfaces are retrieved in the same order as they are registered. @param pEnv this environment @param ppInterface inout parameter for the registered interface; (0) if none was found @param pOId object id of interface to be retrieved @param pTypeDescr type description of interface to be retrieved */ void (SAL_CALL * getRegisteredInterface)( uno_ExtEnvironment * pEnv, void ** ppInterface, rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr ); /** Returns all currently registered interfaces of this environment. The memory block allocated might be slightly larger than (*pnLen * sizeof(void *)). @param pEnv this environment @param pppInterfaces out param; pointer to array of interface pointers @param pnLen out param; length of array @param memAlloc function for allocating memory that is passed back */ void (SAL_CALL * getRegisteredInterfaces)( uno_ExtEnvironment * pEnv, void *** pppInterfaces, sal_Int32 * pnLen, uno_memAlloc memAlloc ); /* ===== the following part will be late initialized by a matching bridge ===== * * ===== and is NOT for public use. ===== */ /** Computes an object id of the given interface; is called by the environment implementation. @param pEnv corresponding environment @param ppOId out param: computed id @param pInterface an interface */ void (SAL_CALL * computeObjectIdentifier)( uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface ); /** Function to acquire an interface. @param pEnv corresponding environment @param pInterface an interface */ void (SAL_CALL * acquireInterface)( uno_ExtEnvironment * pEnv, void * pInterface ); /** Function to release an interface. @param pEnv corresponding environment @param pInterface an interface */ void (SAL_CALL * releaseInterface)( uno_ExtEnvironment * pEnv, void * pInterface ); } uno_ExtEnvironment;
There is a distinction between registered environments
and anonymous environments. You can obtain an existing environment by
calling uno_getEnvironment()
.
If there is no existing one, then uno_getEnvironment()
creates and registers a default one providing the additional functionality
(interface registration etc.). This is the common way.
In contrast to this you can call uno_createEnvironent()
to create an anonymous environment giving an environment's type name
(e.g. "msci") and a context pointer. Creating anonymous environments
is sensible if you need more than one environment of the same type.
You may want to protocol any calls from/ to your component and one
possibility is the following approach:
- Create an anonymous environment of type "uno".
- Connect the registered uno environment and the anonymous environment via the protocol bridge "prot_uno_uno".
- Connect the anonymous UNO environment with the environment of your component using the C++ bridge of choice (compiler that compiled the component code).
- Connect your launching environment (the environment from within calls are put) with the registered uno environment.
- Launch your component.
Mapping
A bridge consists of two mappings. Each mapping is dependent on its counterpart mapping, because performing a call may bring up the need to convert interfaces from one environment to the other (e.g., in parameter interface) and vice versa (e.g., return values).
Mapping an interface from environment A to environment B involves several steps to keep track of object identities:
- First the object identifier of an interface is determined by calling getObjectIdentifier() of environment A (source environment).
- Then the destination environment is asked with the object identifier and type, if there is a already a registered interface in use. If this is the case you can use that one and end up here.
- If there is no such interface in use, the bridge will produce a proxy of that type delegating all calls to the given source interface of environment A.
- The source interface will be registered at environment A and the proxy interface will be introduced as a new proxy at environment B (registerProxyInterface()).
The whole scenario is shown in the big picture.
Microsoft Visual C++ - UNO bridge
This is a short, source code based description of an UNO bridge implementation for C++ objects compiled with the Microsoft Visual C++ 4-6. It covers the rudimentary calling stuff which is very similar to other compilers but omits exception handling.
Mapping
Mapping an interface from one environment to another is transparently done via the right mapping, e.g. like the following XFoo interface from UNO to the Microsoft Visual C++ environment:
Mapping aMapping( "uno", "msci" ); XFoo * pFoo = (XFoo *)aMapping.mapInterface( pUnoFoo, ::getCppuType( (const Reference< XFoo > *)0 ) ); ... pFoo->bar(); ... pFoo->release();
The given UNO interface is mapped to the Microsoft world transparently, so you can call methods and catch exceptions as if the calls are performed on an "original" Visual C++ object.
To claim this goal, the underlying bridge has to emulate the behaviour and keep the correct object layout of its compiler. This example explains the bridge from the Microsoft Visual C++ compiler to the binary (C-) UNO specification which is system dependent, but not compiler dependent. You can communicate with UNO objects compiled with another compiler just by having an appropriate bridge to UNO.
C++ Proxy
Under the hood the call bar()
that is performed on the mapped
interface will call on a proxy C++ object that delegates the call to its
corresponding UNO interface (the one given to mapInterface()
). To
create a C++ proxy, there is already a proxy class you can modify for your
needs (header file
bridges/inc/cpp_uno/bridge.h).
You usually instantiate this class and modify the vtable pointer, giving your
generic vtable. For Microsoft Visual C++ you can use a generic one, because the
objects' this pointer is anytime the second stack parameter. On gcc or sunpro5
the first parameter may be the pointer to a struct return space. So for those
compilers you have to generated a vtable for each type that is used.
When the proxy interface is called, the vtable index is determined by the generic vtable and based on this, the method type description. This is the information to get the values from the processor call stack and perform a dispatch call on the target UNO interface that the C++ proxy is wrapping.
After the dispatch call has been done, the returned exception information is checked whether a C++ exception has to be generated and raised. If no exception has occurred, the inout/ out parameters have to be reconverted (which is only important for values representing interfaces or values containing interfaces though, because all UNO values are binary compatible on a specific computing architecture).
The C++ proxy object holds the interface origin (i.e. the target uno interface) so it can register itself at the environment on its first acquire() and revoke itself on its last release() from its environment.
struct cppu_cppInterfaceProxy : public ::com::sun::star::uno::XInterface { oslInterlockedCount nRef; cppu_Bridge * pBridge; // mapping information uno_Interface * pUnoI; // wrapped interface typelib_InterfaceTypeDescription * pTypeDescr; ::rtl::OUString oid; // non virtual methods called on incoming vtable calls #1, #2 inline void SAL_CALL acquireProxy(); inline void SAL_CALL releaseProxy(); // XInterface: these are only here for dummy, there will be a patched vtable! virtual ::com::sun::star::uno::Any SAL_CALL queryInterface( const ::com::sun::star::uno::Type & ) { return ::com::sun::star::uno::Any(); } // don't use this, use cppu_queryInterface()! virtual void SAL_CALL acquire() {} // don't use this, use cppu_acquire()! virtual void SAL_CALL release() {} // don't use this, use cppu_release()! // ctor inline cppu_cppInterfaceProxy( cppu_Bridge * pBridge_, uno_Interface * pUnoI_, typelib_InterfaceTypeDescription * pTypeDescr_, const ::rtl::OUString & rOId_ ); };
The proxy manages its reference count, a pointer to its bridge to get the counterpart mapping, the uno interface it delegates calls to, the (interface) type it is emulating and an object identifier (oid).
The type and object identifier are needed to manage objects from several environments and prove for object identity and to improve performance (no new interface is needed if there is already a registered interface in the environment).
Essentially if a proxy object is created by the Visual C++ compiler, its vtable is patched, which is the very first pointer in the object layout:
void SAL_CALL cppu_cppInterfaceProxy_patchVtable( XInterface * pCppI, typelib_InterfaceTypeDescription * pTypeDescr ) { static MediateVtables * s_pMediateVtables = 0; if (! s_pMediateVtables) { MutexGuard aGuard( Mutex::getGlobalMutex() ); if (! s_pMediateVtables) { static MediateVtables s_aMediateVtables; s_pMediateVtables = &s_aMediateVtables; } } // nMapFunctionIndexToMemberIndex: minimum size of demanded vtable *(const void **)pCppI = s_pMediateVtables->getMediateVtable( pTypeDescr->nMapFunctionIndexToMemberIndex ); }
The call stack of a virtual function call looks like this:
Offset: |
Value: |
0 |
return address |
4 |
this pointer |
[if struct] 8 |
[if struct] pointer to return struct |
8 | 12 |
parameter 0 |
... |
... |
The proxy vtable (i.e. function pointer array to perform polymorphic calls on C++ objects) determines which function should be called (there is an initial vtable of 256 function slots that is used for any proxy while no proxy demands a larger one):
Offset: |
Function pointer: |
0 |
|
4 |
|
8 |
|
12 |
|
... |
... |
The only task of the proxy vtable for Visual C++ is to determine the call index of the uno method that is to be called, so the proxy vtable looks like:
Offset: |
Function pointer to code: |
0 |
mov eax, 0
|
4 |
mov eax, 1
|
8 |
mov eax, 2
|
... |
... |
First the bar()
call reaches the assembler snippet which
determines the vtable slot and jumps to a function called
cpp_vtable_call()
:
/** * is called on incoming vtable calls * (called by asm snippets) */ static __declspec(naked) void __cdecl cpp_vtable_call(void) { __asm { sub esp, 8 // space for immediate return type push esp push eax // vtable index mov eax, esp add eax, 16 push eax // original stack ptr call cpp_mediate add esp, 12 cmp eax, typelib_TypeClass_FLOAT je Lfloat cmp eax, typelib_TypeClass_DOUBLE je Ldouble cmp eax, typelib_TypeClass_HYPER je Lhyper cmp eax, typelib_TypeClass_UNSIGNED_HYPER je Lhyper // rest is eax pop eax add esp, 4 ret Lhyper: pop eax pop edx ret Lfloat: fld dword ptr [esp] add esp, 8 ret Ldouble: fld qword ptr [esp] add esp, 8 ret } }
The cpp_vtable_call()
function just calls
cpp_mediate()
providing some space for return values returned in
registers, the vtable slot and original stack pointer. The
cpp_mediate()
function returns the type class of the return value
in eax. The type class is used to determine where to place return values.
static typelib_TypeClass __cdecl cpp_mediate( void ** pCallStack, sal_Int32 nVtableCall, sal_Int64 * pRegisterReturn /* space for register return */ ) { OSL_ENSHURE( sizeof(sal_Int32)==sizeof(void *), "### unexpected!" ); // pCallStack: ret adr, this, [ret *], params // _this_ ptr is patched cppu_XInterfaceProxy object cppu_cppInterfaceProxy * pThis = static_cast< cppu_cppInterfaceProxy * >( reinterpret_cast< XInterface * >( pCallStack[1] ) ); typelib_InterfaceTypeDescription * pTypeDescr = pThis->pTypeDescr; OSL_ENSHURE( nVtableCall < pTypeDescr->nMapFunctionIndexToMemberIndex, "### illegal vtable index!" ); if (nVtableCall >= pTypeDescr->nMapFunctionIndexToMemberIndex) { throw RuntimeException( OUString( RTL_CONSTASCII_USTRINGPARAM("illegal vtable index!") ), (XInterface *)pThis ); } // determine called method sal_Int32 nMemberPos = pTypeDescr->pMapFunctionIndexToMemberIndex[nVtableCall]; OSL_ENSHURE( nMemberPos < pTypeDescr->nAllMembers, "### illegal member index!" ); TypeDescription aMemberDescr( pTypeDescr->ppAllMembers[nMemberPos] ); typelib_TypeClass eRet; switch (aMemberDescr.get()->eTypeClass) { case typelib_TypeClass_INTERFACE_ATTRIBUTE: { if (pTypeDescr->pMapMemberIndexToFunctionIndex[nMemberPos] == nVtableCall) { // is GET method eRet = cpp2uno_call( pThis, aMemberDescr.get(), ((typelib_InterfaceAttributeTypeDescription *)aMemberDescr.get())->pAttributeTypeRef, 0, 0, // no params pCallStack, pRegisterReturn ); } else { // is SET method typelib_MethodParameter aParam; aParam.pTypeRef = ((typelib_InterfaceAttributeTypeDescription *)aMemberDescr.get())->pAttributeTypeRef; aParam.bIn = sal_True; aParam.bOut = sal_False; eRet = cpp2uno_call( pThis, aMemberDescr.get(), 0, // indicates void return 1, &aParam, pCallStack, pRegisterReturn ); } break; } case typelib_TypeClass_INTERFACE_METHOD: { // is METHOD switch (nVtableCall) { // standard XInterface vtable calls case 1: // acquire() pThis->acquireProxy(); // non virtual call! eRet = typelib_TypeClass_VOID; break; case 2: // release() pThis->releaseProxy(); // non virtual call! eRet = typelib_TypeClass_VOID; break; case 0: // queryInterface() opt { typelib_TypeDescription * pTD = 0; TYPELIB_DANGER_GET( &pTD, reinterpret_cast< Type * >( pCallStack[3] )->getTypeLibType() ); OSL_ASSERT( pTD ); XInterface * pInterface = 0; (*pThis->pBridge->pCppEnv->getRegisteredInterface)( pThis->pBridge->pCppEnv, (void **)&pInterface, pThis->oid.pData, (typelib_InterfaceTypeDescription *)pTD ); if (pInterface) { uno_any_construct( reinterpret_cast< uno_Any * >( pCallStack[2] ), &pInterface, pTD, cpp_acquire ); pInterface->release(); TYPELIB_DANGER_RELEASE( pTD ); *(void **)pRegisterReturn = pCallStack[2]; eRet = typelib_TypeClass_ANY; break; } TYPELIB_DANGER_RELEASE( pTD ); } // else perform queryInterface() default: eRet = cpp2uno_call( pThis, aMemberDescr.get(), ((typelib_InterfaceMethodTypeDescription *)aMemberDescr.get())->pReturnTypeRef, ((typelib_InterfaceMethodTypeDescription *)aMemberDescr.get())->nParams, ((typelib_InterfaceMethodTypeDescription *)aMemberDescr.get())->pParams, pCallStack, pRegisterReturn ); } break; } default: { throw RuntimeException( OUString( RTL_CONSTASCII_USTRINGPARAM("no member description found!") ), (XInterface *)pThis ); // is here for dummy eRet = typelib_TypeClass_VOID; } } return eRet; }
The cpp_mediate()
function looks up the called vtable index, gets the attribute or method type description, and calls cpp2uno_call()
, performing the actual UNO dispatch call.
An interesting optimization is done on queryInterface()
(vtable
slot 0): Instead of performing the call, the source environment is asked for a
registered interface. If there is no registered interface, then the call has to
be performed.
static typelib_TypeClass cpp2uno_call( cppu_cppInterfaceProxy * pThis, const typelib_TypeDescription * pMemberTypeDescr, typelib_TypeDescriptionReference * pReturnTypeRef, // 0 indicates void return sal_Int32 nParams, typelib_MethodParameter * pParams, void ** pCallStack, sal_Int64 * pRegisterReturn /* space for register return */ ) { // pCallStack: ret, this, [complex return ptr], params char * pCppStack = (char *)(pCallStack +2); // return typelib_TypeDescription * pReturnTypeDescr = 0; if (pReturnTypeRef) TYPELIB_DANGER_GET( &pReturnTypeDescr, pReturnTypeRef ); void * pUnoReturn = 0; void * pCppReturn = 0; // complex return ptr: if != 0 && != pUnoReturn, reconversion need if (pReturnTypeDescr) { if (cppu_isSimpleType( pReturnTypeDescr )) { pUnoReturn = pRegisterReturn; // direct way for simple types } else // complex return via ptr (pCppReturn) { pCppReturn = *(void **)pCppStack; pCppStack += sizeof(void *); pUnoReturn = (cppu_relatesToInterface( pReturnTypeDescr ) ? alloca( pReturnTypeDescr->nSize ) : pCppReturn); // direct way } } // stack space OSL_ENSHURE( sizeof(void *) == sizeof(sal_Int32), "### unexpected size!" ); // parameters void ** pUnoArgs = (void **)alloca( 4 * sizeof(void *) * nParams ); void ** pCppArgs = pUnoArgs + nParams; // indices of values this have to be converted (interface conversion cpp<=>uno) sal_Int32 * pTempIndizes = (sal_Int32 *)(pUnoArgs + (2 * nParams)); // type descriptions for reconversions typelib_TypeDescription ** ppTempParamTypeDescr = (typelib_TypeDescription **)(pUnoArgs + (3 * nParams)); sal_Int32 nTempIndizes = 0; for ( sal_Int32 nPos = 0; nPos < nParams; ++nPos ) { const typelib_MethodParameter & rParam = pParams[nPos]; typelib_TypeDescription * pParamTypeDescr = 0; TYPELIB_DANGER_GET( &pParamTypeDescr, rParam.pTypeRef ); if (!rParam.bOut && cppu_isSimpleType( pParamTypeDescr )) // value { pCppArgs[nPos] = pCppStack; pUnoArgs[nPos] = pCppStack; switch (pParamTypeDescr->eTypeClass) { case typelib_TypeClass_HYPER: case typelib_TypeClass_UNSIGNED_HYPER: case typelib_TypeClass_DOUBLE: pCppStack += sizeof(sal_Int32); // extra long } // no longer needed TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } else // ptr to complex value | ref { pCppArgs[nPos] = *(void **)pCppStack; if (! rParam.bIn) // is pure out { // uno out is unconstructed mem! pUnoArgs[nPos] = alloca( pParamTypeDescr->nSize ); pTempIndizes[nTempIndizes] = nPos; // will be released at reconversion ppTempParamTypeDescr[nTempIndizes++] = pParamTypeDescr; } // is in/inout else if (cppu_relatesToInterface( pParamTypeDescr )) { uno_copyAndConvertData( pUnoArgs[nPos] = alloca( pParamTypeDescr->nSize ), *(void **)pCppStack, pParamTypeDescr, &pThis->pBridge->aCpp2Uno ); pTempIndizes[nTempIndizes] = nPos; // has to be reconverted // will be released at reconversion ppTempParamTypeDescr[nTempIndizes++] = pParamTypeDescr; } else // direct way { pUnoArgs[nPos] = *(void **)pCppStack; // no longer needed TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } } pCppStack += sizeof(sal_Int32); // standard parameter length } // ExceptionHolder uno_Any aUnoExc; // Any will be constructed by callee uno_Any * pUnoExc = &aUnoExc; // invoke uno dispatch call (*pThis->pUnoI->pDispatcher)( pThis->pUnoI, pMemberTypeDescr, pUnoReturn, pUnoArgs, &pUnoExc ); // in case an exception occurred... if (pUnoExc) { // destruct temporary in/inout params while (nTempIndizes--) { sal_Int32 nIndex = pTempIndizes[nTempIndizes]; if (pParams[nIndex].bIn) // is in/inout => was constructed uno_destructData( pUnoArgs[nIndex], ppTempParamTypeDescr[nTempIndizes], 0 ); TYPELIB_DANGER_RELEASE( ppTempParamTypeDescr[nTempIndizes] ); } if (pReturnTypeDescr) TYPELIB_DANGER_RELEASE( pReturnTypeDescr ); msci_raiseException( &aUnoExc, &pThis->pBridge->aUno2Cpp ); // has to destruct the any // is here for dummy return typelib_TypeClass_VOID; } else // else no exception occurred... { // temporary params while (nTempIndizes--) { sal_Int32 nIndex = pTempIndizes[nTempIndizes]; typelib_TypeDescription * pParamTypeDescr = ppTempParamTypeDescr[nTempIndizes]; if (pParams[nIndex].bOut) // inout/out { // convert and assign uno_destructData( pCppArgs[nIndex], pParamTypeDescr, cpp_release ); uno_copyAndConvertData( pCppArgs[nIndex], pUnoArgs[nIndex], pParamTypeDescr, &pThis->pBridge->aUno2Cpp ); } // destroy temp uno param uno_destructData( pUnoArgs[nIndex], pParamTypeDescr, 0 ); TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } // return if (pCppReturn) // has complex return { if (pUnoReturn != pCppReturn) // needs reconversion { uno_copyAndConvertData( pCppReturn, pUnoReturn, pReturnTypeDescr, &pThis->pBridge->aUno2Cpp ); // destroy temp uno return uno_destructData( pUnoReturn, pReturnTypeDescr, 0 ); } // complex return ptr is set to eax *(void **)pRegisterReturn = pCppReturn; } if (pReturnTypeDescr) { typelib_TypeClass eRet = (typelib_TypeClass)pReturnTypeDescr->eTypeClass; TYPELIB_DANGER_RELEASE( pReturnTypeDescr ); return eRet; } else return typelib_TypeClass_VOID; } }
The cpp2uno_call()
function reads C++ parameters from the call
stack and converts to binary (C-) UNO, if needed (C++ and UNO values are binary
compatible concerning the memory layout). If the UNO dispatch call has
returned and no exception has been signalled (pUnoExc
), all out
parameters are written back to C++.
UNO Stub
If a Visual C++ interface is be mapped to binary UNO (i.e. the intermediate environment), and that interface is mapped to another compiler environment, then an UNO stub is created , delegating all UNO dispatch calls to its corresponding C++ interface (in this case Microsoft Visual C++).
Incoming calls on the UNO interface (i.e. calls of the dispatch function)
are performed by converting the call parameters and pushing them on the
processor stack, then calling the demanded vtable slot on the destination
interface.
Any C++ exception is caught and reported as an out parameter to
the caller. If no exception occurred, inout/ out parameters are converted and
written.
The UNO stub implementation is referred as the
cppu_unoInterfaceProxy
class, because it functions as an UNO proxy
of a C++ interface:
struct cppu_unoInterfaceProxy : public uno_Interface { oslInterlockedCount nRef; cppu_Bridge * pBridge; // mapping information ::com::sun::star::uno::XInterface * pCppI; // wrapped interface typelib_InterfaceTypeDescription * pTypeDescr; ::rtl::OUString oid; // ctor inline cppu_unoInterfaceProxy( cppu_Bridge * pBridge_, ::com::sun::star::uno::XInterface * pCppI_, typelib_InterfaceTypeDescription * pTypeDescr_, const ::rtl::OUString & rOId_ ); };
The UNO stub manages its reference count, a pointer to its bridge to access its counterpart mapping, the C++ interface it delegates incoming dispatch calls to, the (interface) type it is emulating and an object identifier (oid).
The dispatch function uno_Interface::pDispatcher()
distinguishes between attribute read/ write access and method calls from the
UNO perspective, calling the correct C++ virtual function:
void SAL_CALL cppu_unoInterfaceProxy_dispatch( uno_Interface * pUnoI, const typelib_TypeDescription * pMemberDescr, void * pReturn, void * pArgs[], uno_Any ** ppException ) { // is my surrogate cppu_unoInterfaceProxy * pThis = static_cast< cppu_unoInterfaceProxy * >( pUnoI ); typelib_InterfaceTypeDescription * pTypeDescr = pThis->pTypeDescr; switch (pMemberDescr->eTypeClass) { case typelib_TypeClass_INTERFACE_ATTRIBUTE: { // determine vtable call index sal_Int32 nMemberPos = ((typelib_InterfaceMemberTypeDescription *)pMemberDescr)->nPosition; OSL_ENSHURE( nMemberPos < pTypeDescr->nAllMembers, "### member pos out of range!" ); sal_Int32 nVtableCall = pTypeDescr->pMapMemberIndexToFunctionIndex[nMemberPos]; OSL_ENSHURE( nVtableCall < pTypeDescr->nMapFunctionIndexToMemberIndex, "### illegal vtable index!" ); typelib_TypeDescriptionReference * pRuntimeExcRef = 0; if (pReturn) { // dependent dispatch cpp_call( pThis, nVtableCall, ((typelib_InterfaceAttributeTypeDescription *)pMemberDescr)->pAttributeTypeRef, 0, 0, // no params 1, &pRuntimeExcRef, // RuntimeException pReturn, pArgs, ppException ); } else { // is SET typelib_MethodParameter aParam; aParam.pTypeRef = ((typelib_InterfaceAttributeTypeDescription *)pMemberDescr)->pAttributeTypeRef; aParam.bIn = sal_True; aParam.bOut = sal_False; typelib_TypeDescriptionReference * pReturnTypeRef = 0; OUString aVoidName( RTL_CONSTASCII_USTRINGPARAM("void") ); typelib_typedescriptionreference_new( &pReturnTypeRef, typelib_TypeClass_VOID, aVoidName.pData ); // dependent dispatch cpp_call( pThis, nVtableCall +1, // get, then set method pReturnTypeRef, 1, &aParam, 1, &pRuntimeExcRef, pReturn, pArgs, ppException ); typelib_typedescriptionreference_release( pReturnTypeRef ); } break; } case typelib_TypeClass_INTERFACE_METHOD: { // determine vtable call index sal_Int32 nMemberPos = ((typelib_InterfaceMemberTypeDescription *)pMemberDescr)->nPosition; OSL_ENSHURE( nMemberPos < pTypeDescr->nAllMembers, "### member pos out of range!" ); sal_Int32 nVtableCall = pTypeDescr->pMapMemberIndexToFunctionIndex[nMemberPos]; OSL_ENSHURE( nVtableCall < pTypeDescr->nMapFunctionIndexToMemberIndex, "### illegal vtable index!" ); switch (nVtableCall) { // standard calls case 1: // acquire uno interface (*pUnoI->acquire)( pUnoI ); *ppException = 0; break; case 2: // release uno interface (*pUnoI->release)( pUnoI ); *ppException = 0; break; case 0: // queryInterface() opt { typelib_TypeDescription * pTD = 0; TYPELIB_DANGER_GET( &pTD, reinterpret_cast< Type * >( pArgs[0] )->getTypeLibType() ); OSL_ASSERT( pTD ); uno_Interface * pInterface = 0; (*pThis->pBridge->pUnoEnv->getRegisteredInterface)( pThis->pBridge->pUnoEnv, (void **)&pInterface, pThis->oid.pData, (typelib_InterfaceTypeDescription *)pTD ); if (pInterface) { uno_any_construct( reinterpret_cast< uno_Any * >( pReturn ), &pInterface, pTD, 0 ); (*pInterface->release)( pInterface ); TYPELIB_DANGER_RELEASE( pTD ); *ppException = 0; break; } TYPELIB_DANGER_RELEASE( pTD ); } // else perform queryInterface() default: // dependent dispatch cpp_call( pThis, nVtableCall, ((typelib_InterfaceMethodTypeDescription *)pMemberDescr)->pReturnTypeRef, ((typelib_InterfaceMethodTypeDescription *)pMemberDescr)->nParams, ((typelib_InterfaceMethodTypeDescription *)pMemberDescr)->pParams, ((typelib_InterfaceMethodTypeDescription *)pMemberDescr)->nExceptions, ((typelib_InterfaceMethodTypeDescription *)pMemberDescr)->ppExceptions, pReturn, pArgs, ppException ); } break; } default: { ::com::sun::star::uno::RuntimeException aExc( OUString( RTL_CONSTASCII_USTRINGPARAM("illegal member type description!") ), pThis->pCppI ); typelib_TypeDescription * pTD = 0; const Type & rExcType = ::getCppuType( (const ::com::sun::star::uno::RuntimeException *)0 ); TYPELIB_DANGER_GET( &pTD, rExcType.getTypeLibType() ); uno_any_construct( *ppException, &aExc, pTD, 0 ); TYPELIB_DANGER_RELEASE( pTD ); } } }
Further on cpp_call()
is called given the C++ interface
pointer, vtable index and all parameters to perform the C++ virtual function
call. The given parameters are still binary (C-) UNO values that may be
converted to fit the compiler environment (i.e. an UNO interface must be mapped
to a C++ interface). The cpp_call()
function prepares an array of
longs (stack parameters) and calls callVirtualMethod()
, an
assembly function performing the Microsoft specific virtual call having the
right registers set:
static void cpp_call( cppu_unoInterfaceProxy * pThis, sal_Int32 nVtableCall, typelib_TypeDescriptionReference * pReturnTypeRef, sal_Int32 nParams, typelib_MethodParameter * pParams, sal_Int32 nExceptions, typelib_TypeDescriptionReference ** ppExceptionRefs, void * pUnoReturn, void * pUnoArgs[], uno_Any ** ppUnoExc ) { // max space for: [complex ret ptr], values|ptr ... char * pCppStack = (char *)alloca( sizeof(sal_Int32) + (nParams * sizeof(sal_Int64)) ); char * pCppStackStart = pCppStack; // return typelib_TypeDescription * pReturnTypeDescr = 0; TYPELIB_DANGER_GET( &pReturnTypeDescr, pReturnTypeRef ); OSL_ENSHURE( pReturnTypeDescr, "### expected return type description!" ); void * pCppReturn = 0; // if != 0 && != pUnoReturn, needs reconversion if (pReturnTypeDescr) { if (cppu_isSimpleType( pReturnTypeDescr )) { pCppReturn = pUnoReturn; // direct way for simple types } else { // complex return via ptr pCppReturn = *(void **)pCppStack = (cppu_relatesToInterface( pReturnTypeDescr ) ? alloca( pReturnTypeDescr->nSize ) : pUnoReturn); // direct way pCppStack += sizeof(void *); } } // stack space OSL_ENSHURE( sizeof(void *) == sizeof(sal_Int32), "### unexpected size!" ); // args void ** pCppArgs = (void **)alloca( 3 * sizeof(void *) * nParams ); // indices of values this have to be converted (interface conversion cpp<=>uno) sal_Int32 * pTempIndizes = (sal_Int32 *)(pCppArgs + nParams); // type descriptions for reconversions typelib_TypeDescription ** ppTempParamTypeDescr = (typelib_TypeDescription **)(pCppArgs + (2 * nParams)); sal_Int32 nTempIndizes = 0; for ( sal_Int32 nPos = 0; nPos < nParams; ++nPos ) { const typelib_MethodParameter & rParam = pParams[nPos]; typelib_TypeDescription * pParamTypeDescr = 0; TYPELIB_DANGER_GET( &pParamTypeDescr, rParam.pTypeRef ); if (!rParam.bOut && cppu_isSimpleType( pParamTypeDescr )) { uno_copyAndConvertData( pCppArgs[nPos] = pCppStack, pUnoArgs[nPos], pParamTypeDescr, &pThis->pBridge->aUno2Cpp ); switch (pParamTypeDescr->eTypeClass) { case typelib_TypeClass_HYPER: case typelib_TypeClass_UNSIGNED_HYPER: case typelib_TypeClass_DOUBLE: pCppStack += sizeof(sal_Int32); // extra long } // no longer needed TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } else // ptr to complex value | ref { if (! rParam.bIn) // is pure out { // cpp out is constructed mem, uno out is not! uno_constructData( *(void **)pCppStack = pCppArgs[nPos] = alloca( pParamTypeDescr->nSize ), pParamTypeDescr ); pTempIndizes[nTempIndizes] = nPos; // default constructed for cpp call // will be released at reconversion ppTempParamTypeDescr[nTempIndizes++] = pParamTypeDescr; } // is in/inout else if (cppu_relatesToInterface( pParamTypeDescr )) { uno_copyAndConvertData( *(void **)pCppStack = pCppArgs[nPos] = alloca( pParamTypeDescr->nSize ), pUnoArgs[nPos], pParamTypeDescr, &pThis->pBridge->aUno2Cpp ); pTempIndizes[nTempIndizes] = nPos; // has to be reconverted // will be released at reconversion ppTempParamTypeDescr[nTempIndizes++] = pParamTypeDescr; } else // direct way { *(void **)pCppStack = pCppArgs[nPos] = pUnoArgs[nPos]; // no longer needed TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } } pCppStack += sizeof(sal_Int32); // standard parameter length } // only try-finally/ try-except statements possible... __try { __try { // pCppI is msci this pointer callVirtualMethod( pThis->pCppI, nVtableCall, pCppReturn, pReturnTypeDescr->eTypeClass, (sal_Int32 *)pCppStackStart, (pCppStack - pCppStackStart) / sizeof(sal_Int32) ); // NO exception occurred... *ppUnoExc = 0; // reconvert temporary params while (nTempIndizes--) { sal_Int32 nIndex = pTempIndizes[nTempIndizes]; typelib_TypeDescription * pParamTypeDescr = ppTempParamTypeDescr[nTempIndizes]; if (pParams[nIndex].bIn) { if (pParams[nIndex].bOut) // inout { uno_destructData( pUnoArgs[nIndex], pParamTypeDescr, 0 ); // destroy uno value uno_copyAndConvertData( pUnoArgs[nIndex], pCppArgs[nIndex], pParamTypeDescr, &pThis->pBridge->aCpp2Uno ); } } else // pure out { uno_copyAndConvertData( pUnoArgs[nIndex], pCppArgs[nIndex], pParamTypeDescr, &pThis->pBridge->aCpp2Uno ); } // destroy temp cpp param => cpp: every param was constructed uno_destructData( pCppArgs[nIndex], pParamTypeDescr, cpp_release ); TYPELIB_DANGER_RELEASE( pParamTypeDescr ); } // return value if (pCppReturn && pUnoReturn != pCppReturn) { uno_copyAndConvertData( pUnoReturn, pCppReturn, pReturnTypeDescr, &pThis->pBridge->aCpp2Uno ); uno_destructData( pCppReturn, pReturnTypeDescr, cpp_release ); } } __except (msci_filterCppException( GetExceptionInformation(), *ppUnoExc, &pThis->pBridge->aCpp2Uno )) { // *ppUnoExc is untouched and any was constructed by filter function // __finally block will be called return; } } __finally { // cleanup of params was already done in reconversion loop if no exception occurred; // this is quicker than getting all param descriptions twice! // so cleanup only if an exception occurred: if (*ppUnoExc) { // temporary params while (nTempIndizes--) { sal_Int32 nIndex = pTempIndizes[nTempIndizes]; // destroy temp cpp param => cpp: every param was constructed uno_destructData( pCppArgs[nIndex], ppTempParamTypeDescr[nTempIndizes], cpp_release ); TYPELIB_DANGER_RELEASE( ppTempParamTypeDescr[nTempIndizes] ); } } // return type if (pReturnTypeDescr) TYPELIB_DANGER_RELEASE( pReturnTypeDescr ); } }
Finally callVirtualMethod()
performs the C++ call, copying the given stack parameters and storing return values passed back in registers.
static void callVirtualMethod( void * pThis, sal_Int32 nVtableIndex, void * pRegisterReturn, typelib_TypeClass eReturnTypeClass, sal_Int32 * pStackLongs, sal_Int32 nStackLongs ) { // parameter list is mixed list of * and values // reference parameters are pointers OSL_ENSHURE( pStackLongs && pThis, "### null ptr!" ); OSL_ENSHURE( (sizeof(void *) == 4) && (sizeof(sal_Int32) == 4), "### unexpected size of int!" ); __asm { mov eax, nStackLongs test eax, eax je Lcall // copy values mov ecx, eax shl eax, 2 // sizeof(sal_Int32) == 4 add eax, pStackLongs // params stack space Lcopy: sub eax, 4 push dword ptr [eax] dec ecx jne Lcopy Lcall: // call mov ecx, pThis push ecx // this ptr mov edx, [ecx] // pvft mov eax, nVtableIndex shl eax, 2 // sizeof(void *) == 4 add edx, eax call [edx] // interface method call must be __cdecl!!! // register return mov ecx, eReturnTypeClass cmp ecx, typelib_TypeClass_VOID je Lcleanup mov ebx, pRegisterReturn // int32 cmp ecx, typelib_TypeClass_LONG je Lint32 cmp ecx, typelib_TypeClass_UNSIGNED_LONG je Lint32 cmp ecx, typelib_TypeClass_ENUM je Lint32 // int8 cmp ecx, typelib_TypeClass_BOOLEAN je Lint8 cmp ecx, typelib_TypeClass_BYTE je Lint8 // int16 cmp ecx, typelib_TypeClass_CHAR je Lint16 cmp ecx, typelib_TypeClass_SHORT je Lint16 cmp ecx, typelib_TypeClass_UNSIGNED_SHORT je Lint16 // float cmp ecx, typelib_TypeClass_FLOAT je Lfloat // double cmp ecx, typelib_TypeClass_DOUBLE je Ldouble // int64 cmp ecx, typelib_TypeClass_HYPER je Lint64 cmp ecx, typelib_TypeClass_UNSIGNED_HYPER je Lint64 jmp Lcleanup // no simple type Lint8: mov byte ptr [ebx], al jmp Lcleanup Lint16: mov word ptr [ebx], ax jmp Lcleanup Lfloat: fstp dword ptr [ebx] jmp Lcleanup Ldouble: fstp qword ptr [ebx] jmp Lcleanup Lint64: mov dword ptr [ebx], eax mov dword ptr [ebx+4], edx jmp Lcleanup Lint32: mov dword ptr [ebx], eax jmp Lcleanup Lcleanup: // cleanup stack (obsolete though because of function) mov eax, nStackLongs shl eax, 2 // sizeof(sal_Int32) == 4 add eax, 4 // this ptr add esp, eax } }
Author:
Daniel Bölzle ($Date: 2004/12/08 11:16:23 $) |