|
|
Contents
1 IntroductionThe OLE bridge is a means to allow UNO objects to be accessed in a COM environment, that is to say a COM client can calls an UNO server without having do deal with UNO specific details. Conversely an UNO object can use COM objects and does not have to know anything about COM. The bridge can be incorporated by different languages as long as the languages support OLE. So far the bridge has proved to work reliable with C++ and JScript. VBScript and Visual Basic still needs some testing and as for all other eligible languages they require testing from the ground up. As the name "OLE" bridge implies the bridge deals with OLE objects and not with COM objects generally. That means that only "dispatch objects" can be mapped to UNO and UNO objects are only mapped to dispatch objects. 2 RequirementsFirst of all the system must be able to run both object models. As for COM, it runs on all Windows 95/98, Windows 3.51, Windows 2000 and Windows ME operating systems. There are also ports to Macintosh and Unix and since November 1999 there is a reference implementation for UNIX developed by the Open Group. UNO is available whenever the UDK is installed on a machine or indirectly when an Office is installed. As for the latter, the setup needs to be run to ensure that the initial UNO object is properly registered at the system's registry. Simply copying the executables and libraries won't do. The bridge depends on some additional services. These are com.sun.star.script.Invocation, 3 Parts of the BridgeIn contrast to other language bindings the OLE bridge is based on services. The following paragraphs give an overview of the functionalities which are provided by them. 3.1 Service com.sun.star.bridge.OleBridgeSupplier2Implementation name: com.sun.star.comp.ole.OleConverter2 The service is able to bridge elements of one environment to elements of another environment. This feature is provided by exposing the interface com.sun.star.bridge.XBridgeSupplier2 which looks like
module com { module sun { module star { module bridge {
[ uik(5F97D2F0-50F8-11d4-83240050-04526AB4),
ident("XBridgeSupplier2", 1.0 ) ]
interface XBridgeSupplier2: com::sun::star::uno::XInterface
{
any createBridge( [in] any aModelDepObject,
[in] sequence< byte > aProcessId,
[in] short nSourceModelType,
[in] short nDestModelType )
raises( com::sun::star::lang::IllegalArgumentException );
}; }; }; };
As the parameter nSourceModelType and nDestModelType suggest this interface allows the bridging of element of very different models. The constant group ModelDependent specifies the values which can be used with that interface.
module com { module sun { module star { module bridge {
constants ModelDependent
{
const short UNO = 1;
const short OLE = 2;
const short JAVA = 3;
const short CORBA = 4;
};
}; }; }; };
When using OleBridgeSupplier2 then only the constants UNO and OLE will have effect. The parameter aModelDepObject contains the element that is to be bridged and the return value contains the bridged element. 3.2 Service: com.sun.star.bridge.OleBridgeSupplierVar1Implementation name: com.sun.star.comp.ole.OleConverterVar1 The name OleBridgeSupplierVar1 is admittedly badly chosen and rather nondescript. Anyway, the "Var1" indicates that the service is a variation of the OleBridgeSupplier service. And in fact the functionality is the same but the way it is achieved is partly different. OleConverterVar1 was developed to optimize performance in a remote client server environment. A scenario could look like this: A remote UNO object object is to be converted into an OLE object. Because the bridge can only handle UNO objects which expose XInvocation it utilizes the com.sun.star.script.Invocation service that creates an invocation object out of every UNO object or struct. Invocation has to introspect the respective object which is very expensive in terms of function calls. Now consider Invocation to reside on the client and the actual object on a remote server. Obviously there would be a great many network round trips causing a negative performance impact. OleBridgeSupplier2 is just doing that and hence it is inappropriate for this scenario. OleBridgeSupplierVar1 on the other hand allows the remote creation of Invocation. Since Invocation and the object now reside in the same process the process of creating invocation objects is a lot more efficient. Another issue concerns the way of how the bridge realizes the IDispatch interface for UNO wrapper objects. An UNO wrapper is an object that contains an UNO object and it implements IDispatch so that the wrapper can be used in an OLE environment. Calls on the wrapper's IDispatch are mapped to calls on the XInvocation of the wrapped object. OLE objects are used through their IDispatch interface. A function call or accessing a property is twofold. First IDispatch::GetIDsOfNames is called which basicly takes the function or property name and returns an id (DISPID). With the id at hand the IDispatch::Invoke can be called. The Invoke implementation knows by way of the DISPID exactly what function or property it has been called for. The OleBridgeSupplier2 UNO wrapper checks in GetIDsOfNames whether the object exists or not before it issues a DISPID. The caller knows immediately if the function or property exists. This check is not carried out by OleBridgeSupplierVar1 because it would cause at least one remote call (remember the object is remote). DISPIDs are blindly passed out whenever GetIDsOfNames is being called for a new name. If the DISPID produces an error during the Invoke call then the name is cached, so that the next time GetIDsOfNames is being called the caller receives an appropriate error code. The OleBridgeSupplierVar1 UNO wrapper has to do some more work in IDispatch::Invoke because it has not obtained any type information yet about the meaning of the current call. The wFlags parameter of IDispatch::Invoke can e.g. consist of a combination of DISPATCH_METHOD and DISPATCH_PROPERTYPUT. In this case the wrapper tries first to call a method on the wrapped UNO object and when this fails it tries to access a property. Moreover after a call to XInvocation failed the wrapper checks whether the name is correct by calling XExactName::getExactName that is provided by the invocation object. During the Invoke call information are gathered which are cached so that whenever the same call is made again the cached information are used to fasten up the process. 3.3 com.sun.star.bridge.OleApplicationRegistrationImplementation name: com.sun.star.comp.ole.OleServer This service registers a COM class factory when the service is being instantiated and deregisteres it when the service is being destroyed. The class factory constructs an UNO multi service factory within the OLE environment. Services created by this factory are always created in that environment. That means that someone who is using the COM class factory as starting point for an application which incorporates UNO rarely has to deal with the OLE bridge themselves. The multi service factory which is mapped into the COM environment is the same factory which is passed into the component_getFactory function of the housing DLL. 3.4 com.sun.star.bridge.OleObjectFactoryImplementation name: com.sun.star.comp.ole.OleClient The OleObjectFactory represents itself as multi service factory by exposing the XMultiServiceFactory interface. It is used to create COM objects. The COM components are specified by their programmatic identifier (ProgId). The COM objects are created in the UNO environment, meaning they are wrapped by objects which implement XInvocation and map calls to IDispatch. To map an OLE object into the UNO environment you could create the object through any COM mechanism (e.g. CoCreateInstance) and then convert it by means of the service OleBridgeSupplier2. Actually the OleObjectFactory does not do otherwise. 4 Mapping of TypesThe bridge maps data from UNO to OLE automation types and vice versa. Because the bridge can be used from different languages there are also some language specific issues to be considered; e.g. JScript has no out parameters. The problem is that the bridge cannot tell what language makes use of it, since on the binary level the communication between interfaces complies with the COM specification, regardless of the language. For more information about JScript and the issues involved see chapter 6. The table 1 summarizes the possible conversions from OLE Automation types to UNO types. (The names in italic letters are not idl types)
Table 2 shows the conversions from UNO types to OLE Automation types.
5 Using the bridgeThe bridge is automatically used when someone access the Office through the COM mechanism. This is done by creating the service manager component, that has the ProgId "com.sun.star.ServiceManager". The service manager can then be used to create additional UNO services. There is no explicit mapping from COM to UNO or vice versa necessary. All objects which have been obtained directly or indirectly from the service manager are already COM objects. The ServiceManager implements the XMultiServiceFactory interface, that is used to create UNO services. Bear in mind though, that ServiceManager is a COM object and hence it is to be accessed through the IDispatch interface. Example C++:
// create the service manager of OpenOffice
IDispatch* pdispFactory= NULL;
CLSID clsFactory= {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}};
hr= CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pdispFactory);
// create the CoreReflection service
OLECHAR* funcName= L"createInstance";
DISPID id;
pdispFactory->GetIDsOfNames( IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &id);
VARIANT param1;
VariantInit( ¶m1);
param1.vt= VT_BSTR;
param1.bstrVal= SysAllocString( L"com.sun.star.reflection.CoreReflection");
DISPPARAMS dispparams= { ¶m1, 0, 1, 0}; VARIANT result;
VariantInit( &result);
hr= pdispFactory->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
&dispparams, &result, NULL, 0);
IDispatch* pdispCoreReflection= result.pdispVal;
pdispCoreReflection->AddRef();
// do something with the CoreReflection service
Example JScript:
var factory= new ActiveAObject("com.sun.star.ServiceManager");
var coreReflection= factory.createInstance("com.sun.star.reflection.CoreReflection");
// do something with the CoreReflection service
Irrespective of the Office you can map every COM or UNO elements directly by using the com.sun.star.bridge.OleBridgeSupplier2 service. The code to convert an UNO service could look like this:
// convert the service xIntServiceX to a dispatch object
Any any;
IDispatch* pdisp= NULL;
any <<= xIntServiceX;
sal_uInt8 arId[16];
rtl_getGlobalProcessId( arId);
Any target= xSuppl->createBridge( any, Sequence<sal_Int8>( (sal_Int8*)arId, 16), UNO, OLE);
if (target.getValueTypeClass() == TypeClass_UNSIGNED_LONG)
{
VARIANT* pVariant = *(VARIANT**)target.getValue();
pdisp= pVariant->pdispVal;
pdisp->AddRef()
VariantClear(pVariant);
CoTaskMemFree(pVariant);
}
// do something with pdisp (IDispatch*)
COM objects can be converted as long as they support IDispatch: // create the COM object hr= CoCreateInstance( COMPONENTS_CLSID, NULL,CLSCTX_ALL, IID_Unknown, (void**) &punk); // create the service factory Reference<XInterface> xint= createRegistryServiceFactory( OUString(L"applicat.rdb")); Reference<XMultiServiceFactory> factory= Reference<XMultiServiceFactory>( xint, UNO_QUERY); // create the bridge service Reference< XInterface > xIntSupplier= mgr->createInstance( OUString(L"com.sun.star.bridge.OleBridgeSupplier2")); Reference< XBridgeSupplier2 > xSuppl( xIntSupplier, UNO_QUERY); Any any; Variant var; VariantInit( &var); var.vt= VT_UNKNOWN; var.punkVal= punk; any <<= ( sal_uInt32)&var; sal_uInt8 arId[16]; rtl_getGlobalProcessId( arId); Any target= xSuppl->createBridge( any, Sequence<sal_Int8>( (sal_Int8*)arId, 16), OLE, UNO ); Reference<XInvocation> theObject; target>>= theObject; // theObject contains now the mapped COM object Apart from objects one can map all the data types as shown in table 1 and table 2. A "specialization" of the OleBridgeSupplier2 service is the OleObjectFactory service. It does basically what the above source sample does; just mapping a COM object to XInvocation. 6 Creation of typesThe creation of types of one environment within the same environment is not an issue but it looks different if this is done in another environment. Usually it is not necessary at all because types of one environment are automatically mapped to types of the other environment. In other words, one creates a value of a type which is native to the current environment and passes it to an object function, where the object stems from the other environment. The bridge automatically converts the type if necessary. This even works for UNO interfaces on COM side (see chapter 10). But it is a different matter with UNO structs. When a struct is mapped from UNO to COM then it is encapsulated by an dispatch object and when this object is passed back to UNO then the original struct is extracted. If an UNO object function takes a struct as argument, then it it not possible to create a dispatch object and pass it as the respective argument. This functionality has not been implemented yet. Instead there are two ways of creating structs in the COM environment. First one can make use of the com.sun.star.CoreReflection service:
// create the service manager of OpenOffice
IDispatch* pdispFactory= NULL;
CLSID clsFactory= {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}};
hr= CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pdispFactory);
// create the CoreReflection service
OLECHAR* funcName= L"createInstance";
DISPID id;
pdispFactory->GetIDsOfNames( IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &id);
VARIANT param1;
VariantInit( ¶m1);
param1.vt= VT_BSTR;
param1.bstrVal= SysAllocString( L"com.sun.star.reflection.CoreReflection");
DISPPARAMS dispparams= { ¶m1, 0, 1, 0}; VARIANT result;
VariantInit( &result);
hr= pdispFactory->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
&dispparams, &result, NULL, 0);
IDispatch* pdispCoreReflection= result.pdispVal;
pdispCoreReflection->AddRef();
VariantClear( &result);
// create the struct's idl class object
OLECHAR* strforName= L"forName";
hr= pdispCoreReflection->GetIDsOfNames( IID_NULL, &strforName, 1, LOCALE_USER_DEFAULT, &id);
VariantClear( ¶m1);
param1.vt= VT_BSTR;
param1.bstrVal= SysAllocString(L"com.sun.star.beans.PropertyValue");
hr= pdispCoreReflection->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dispparams, &result, NULL, 0);
IDispatch* pdispClass= result.pdispVal;
pdispClass->AddRef();
VariantClear( &result);
// create the struct
OLECHAR* strcreateObject= L"createObject";
hr= pdispClass->GetIDsOfNames( IID_NULL,&strcreateObject, 1, LOCALE_USER_DEFAULT, &id)
IDispatch* pdispPropertyValue= NULL;
VariantClear( ¶m1);
param1.vt= VT_DISPATCH | VT_BYREF;
param1.ppdispVal= &pdispPropertyValue;
hr= pdispClass->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dispparams, NULL, NULL, 0);
// do something with the struct pdispPropertyValue
pdispPropertyValue->Release();
pdispClass->Release();
pdispCoreReflection->Release();
pdispFactory->Release();
A simpler way is to use the _GetStruct function which every UNO object in a COM environment provides. // object be some UNO object in a COM environment OLECHAR* strstructFunc= L"_GetStruct"; hr= object->GetIDsOfNames( IID_NULL, &strstructFunc, 1, LOCALE_USER_DEFAULT, &id); VariantClear(&result); VariantClear( ¶m1); param1.vt= VT_BSTR; param1.bstrVal= SysAllocString( L"com.sun.star.beans.PropertyValue"); hr= object->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, &result, NULL, 0); IDispatch* pdispPropertyValue= result.pdispVal; pdispPropertyValue->AddRef(); // do something with the struct pdispPropertyValue 7 JScript issuesJScript does not offer arrays in a way as C or C++ do. Instead it provides the Array object which acts as array but is actually a dispatch object. Therefore an UNO object in the COM environment receives an dispatch object when it is used in JScript and an Array object has been passed into a function of that object. // JScript var param= new Array(1,2,3); // object is an UNO object object.function( param); JScript does not has out or in/out parameter. To create those parameters we use Array objects. The value on index 0 (actually property "0") contains the out- value after return of the function. var out= new Array(); object.functionOut(out); var value= out[0]; If an in/out parameter is needed then the in-value is set at index[0]. var inout= new Array(); inout[0]=123; object.functionInOut( inout); var value= inout[0]; Whenever the bridge has to convert a dispatch object then it first has to find out what the object actually represents (object, array, out parameter, in/out parameter) to do an appropriate conversion. This is done by acquiring type information, which itself is not problematic but in a remote environment it causes additional network traffic.To avoid this one could use Value Objects (see chapter 7). Such an object can be used in place of every other parameter in a COM environment. The script programmer sets the type and value of this object, so that the bridge implementation knows what the value actually stands for. JScript always uses doubles whenever it encounters floating point values. When a UNO function is called which actually takes a float as parameter, then the bridge does a conversion by using the Windows function VariantChangeType. This could cause a change of the value depending on the format of the number used in the script. When a UNO function returns a float or has a float as out-parameter then the conversion is put off to JScript. The results of this conversion most certainly does not match the expected value. In a test a UNO function has been called with the number1.234567 where the function expected a float. The function then returned that value as float. JScript receives in this case a value of 1.2345670461654663. A similar situation exist with integer values. JScript exclusively uses 32 bit integer values. The bridge tries to convert to a smaller type if necessary. This works fine as long as the numbers used in JScript correspond to the value range of the parameter type of the respective UNO function argument. 8 Value Objects8.1 MotivationAn UNO object used in JavaScript is wrapped by an object that is COM compatible
in that it exposes 8.2 The Value ObjectA ValueObject is an object that acts as a value of a certain type and can be
passed to UNO functions instead of the actual parameter. A ValueObject is passed
to the wrapper as 8.3 CreationEvery UNO object used by JavaScript is capable of delivering ValueObjects. To obtain one imply call _GetValueObject: // object is some uno wrapper object var valueObject= object._GetValueObject(); 8.4 The Interface of the Value ObjectA Value Object is actually an instance of class
MIDL_INTERFACE("e40a2331-3bc1-11d4-8321-005004526ab4")
IJScriptValueObject: public IUnknown
{
STDMETHOD( Set)( VARIANT type, VARIANT value)= 0;
STDMETHOD( Get)( /*out*/ VARIANT *val)= 0;
STDMETHOD( InitOutParam)()= 0;
STDMETHOD( InitInOutParam)( VARIANT type, VARIANT value)= 0;
STDMETHOD( IsOutParam)( /*out*/ VARIANT_BOOL* flag)= 0;
STDMETHOD( IsInOutParam)( /*out*/VARIANT_BOOL * flag)= 0;
STDMETHOD( GetValue)( /*out*/ BSTR* type,/*out*/ VARIANT *value)= 0;
};
8.5 Supported TypesWhen setting the ValueObject to a value the type is determined by a string that is passed as first parameter in the Set function. The table below shows what strings are currently accepted (column name).
Sequences are represented by prepending "[]", e.g. []char, [][]byte, [][][]object, etc. 8.6 Usage
9 Limitations of the dispatch objectsThe dispatch objects provided by the bridge do not support type information. IDispatch::GetTypeInfoCount and IDispatch::GetTypeInfo return E_NOTIMPL. Moreover there are no COM type libraries available and the dispatch objects do not implement IProvideClassInfo as well. IDispatch::GetIDsOfName has to be called for every name separately. That this one cannot query the ids for several names at a time. IDispatch::Invoke does not support named arguments nor the pExcepInfo and puArgErr parameter. 10 Implementing COM objects with UNO interfaces10.1 IntentionIt is desirable to be able to build COM components which implement UNO interfaces. Those components could be employed as listeners (COM: event sinks), for numerous broadcasters (COM: event source). 10.2 Terms and ConventionsWhen speaking of implementing UNO interfaces in COM components, we do not imply the use of interfaces in terms of abstract C++ classes. Instead the components have to implement IDispatch and dispatch method calls to functions that have the same names as the respective UNO interface functions. Basically all interfaces can be implemented as long as the data type used in the UNO interfaces can be mapped to OLE automation types. The mappings are shown in Table 3. One exception is the XInvocation interface. Because it is itself a scripting interface, it does not make sense to employ it within a COM component. Assuming that someone wants to build a COM component that represents some UNO XInvocation implementation then the properties and functions are to be directly implemented. That is, IDispatch::Invoke dispatches directly to those properties and methods. XInterface does not need to be implemented. Because the 10.3 Components in C++To build a component with C++ one can write the component from scratch or use some kind of framework, where Microsoft's ATL comes in handy. A basic rule with implementing When using ATL there is no need to bother about this as long as one uses a dual interface. Then the parameters correspond to the types as shown in table ... A COM component is free to support as
many UNO interfaces as it needs to but an ATL component just needs to
implement one dual interface. All UNO interface functions have to be
put in that dual. In fact there is no other way because ATL can only
expose one
Table 3 Whenever out or in/out parameter are used then the VARTYPEs above are or'd with VT_BYREF. E.g. there is an UNO interface with this idl description:
interface XSimple : public com.sun.star.uno.XInterface
{
void func1( [in] long val, [out] long outVal);
void func2( [in] sequence< long > val, [out] sequence< long > outVal);
};
The MS IDL description (for an ATL component with a dual interface) would look like:
[
object,
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
dual,
helpstring("ISimple Interface"),
pointer_default(unique)
]
interface ISimple : IDispatch
{
[id(1), helpstring("method func1")]
HRESULT func1( [in] long val, [out] long* outVal);
[id(2), helpstring("method func2")]
HRESULT func2([in] SAFEARRAY(VARIANT) val,
[out] SAFEARRAY(VARIANT) * outVal);
};
The property looks like this:
[propget, id(4), helpstring("property_implementedInterfaces")]
HRESULT _implementedInterfaces([out, retval] SAFEARRAY(BSTR) *pVal);
10.4 Components in JScriptIn JScript one does not have to deal with types. Nevertheless it is important to know how arrays, in/out and out parameter are used because the usage is rather different. For in/out and out parameter an object with a property "0" is passed.
function inoutFunction( val )
{
var value= val[0];
val[0]= 123;
}
function outFunction( val)
{
val[0]= 123;
}
An Array is passed as out parameter:
function outArray( val )
{
val[0]= new Array( 1,2,3);
}
If a JScript object should implement several interfaces than the
object has to have the property "_
function AnObject()
{
this._environment= "JScript";
this._implementedInterfaces= new Array( "XSimple1",
"XSimple2","XSimple3");
// the interface functions
this.simple1Func= simple1Func;
this.simple2Func= simple2Func;
this.simple3Func= simple3Func;
}
// XSimple1
function simpleFunc()
{
...
}
// XSimple2
function simple2Func()
{
...
}
// XSimple3
function simple3Func()
{
...
}
|



