The Ole Bridge |
Contents
- Introduction
- Requirements
- Parts of the bridge
- Mapping of types
- Using the bridge
- Creation of types
- JScript issues
- Value Objects
- Limitation of the dispatch objects
- Implementing COM objects with UNO interfaces
1 Introduction
The 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 Requirements
First 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,
com.sun.star.script.InvocationAdapterFactory,
com.sun.star.script.Converter
com.sun.star.reflection.CoreReflection,
com.sun.star.beans.Introspection.
3 Parts of the Bridge
In 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.OleBridgeSupplier2
Implementation 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.OleBridgeSupplierVar1
Implementation 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.OleApplicationRegistration
Implementation 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.OleObjectFactory
Implementation 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 Types
The 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)
OLE Automation (idl types) | UNO (idl types) |
---|---|
boolean | boolean |
unsigned char | byte |
[1] double | double, float |
float | float |
short | short |
[1] long | long, short, byte |
BSTR | string |
SCODE | unsigned long |
IUnknown* | com.sun.star.script.XInvocation |
[2] IDispatch* | com.sun.star.script.XInvocation interface type, original type. sequence<type>, out parameter, in/out parameter |
[3] SAFEARRAY(type) | sequence<type> |
[4] type | any |
- JScript does not use the types float, char and short. If the target UNO function takes one of those types then an appropriate conversion occurs.
The default behaviour of the conversion routine is that dispatch objects are converted into invocation objects (objects implementing XInvocation). The actual COM object is wrapped by an UNO object that implements XInvocation. Calls on XInvocation are mapped to calls on IDispatch.
If a dispatch object is actually a wrapped UNO object then the original UNO object is extracted. Consider the following JScript (COM environment) code:
// obj is an uno object var obj2= obj.getSomeObject(); //obj2 is an UNO object which is encapsulated by an UNO wrapper obj.putSomeObject( obj2);
When obj2 is passed back into the UNO environment then "obj" receives the original UNO object instead of a COM wrapper object.
When an UNO object in a COM environment receives an dispatch object in an IDispatch::Invoke call then it acquires type information about the parameter. The information contains the type what the dispatch object should represent in the UNO environment. If an interface other than XInvocation is expected than that specific interface is created by way of the service com.sun.star.script.InvocationAdapterFactory.
In JScript one uses Array objects for out or in/out parameters (see JScript issues ). Those objects are dispatch objects which are converted into the expected type. Besides, the Array objects are of course used for arrays. If so then they are converted into a Sequence with the proper element type.
A mapped COM object can only be successfully used through XInvocation if it provides type information through IDispatch::GetTypeInfo.
- A SAFEARRAY is converted to sequence<type> where type is the respective UNO type. If the SAFEARRAY has more than one dimension then the anys contain sequences of sequences and so forth; e.g. sequence<sequence<long>>.
- Whenever an UNO function requires an any parameter then the caller in the
COM environment is not obliged to pass a VARIANT (actually VT_VARIANT | VT_BYREF).
Instead the actual type can be put into the respective VARIANT argument (DISPPARAMS::rgvarg).
If the bridge receives an VT_DISPATCH then it depends of the type information of the target UNO function or property what the dispatch object will be converted into (see [2], JScript issues).
Table 2 shows the conversions from UNO types to OLE Automation types.
UNO | OLE Automation |
---|---|
boolean | boolean |
byte | unsigned char |
double | double |
float | float |
short | short |
unsigned short | short |
long | long |
unsigned long | long |
string | BSTR |
interfaces | IDispatch* |
[1] sequence<type> | SAFEARRAY(type), SAFEARRAY(VARIANT) |
struct | IDispatch* |
enum | long |
char | short |
[2] type | VARIANT |
- A Sequence which has as element
type of a sequence could be regarded as two-dimensional array. The
difference is that the element sequences could have varying lengths
whereas the elements of a certain dimension of a multidimensional
array have the same length. To reflect that, sequences are converted
into one-dimensional SAFEARRAYs with VARIANT elements which in turn
can contain SAFEARRAYs. This conversion works fine with COM objects
which expect those arrays, e.g. COM objects which implement UNO
interfaces (see chapter 10). But quite often this will not
be the
case and if a function argument is a SAFEARRAY of a certain element
type and dimension then exactly this argument has to be provided.
Whenever the bridge determines that a COM object does not implement UNO interfaces then it converts a Sequence into a SAFEARRAY as described by the type information provided through the object's IDispatch interface.
In case IDispatch::Invoke expects an argument of type SAFEARRAY(VARIANT) then the UNO clients can provide sequence<any> or just sequence<type>, where type means the actual UNO type.
- The XBridgeSupplier2::createInstance function returns an any that
contains the converted element. If the target environment is OLE than
the any contains an unsigned long that is actually a VARIANT*. The
VARIANT then contains the converted element and has never the
VT_BYREF bit set. Hence it cannot contain another VARIANT.
When a client uses the COM object in an UNO environment (through XInvocation) and calls a function that requires a VARIANT argument, then the bridge creates the arguments for IDispatch::Invoke as needed. In this case DISPPARAMS::rgvarg would contain a VARIANT with VARTYPE::vt= VT_VARIANT | VT_BYREF. To do this the bridge must use type information which is provided through IDispatch::Invoke.
5 Using the bridge
The 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 types
The 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 issues
JScript 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 Objects
8.1 Motivation
An UNO object used in JavaScript is wrapped by an object that is COM compatible
in that it exposes IDispatch
. Such wrapper may be referred to as
uno wrapper in this document. Every access on a uno wrapper within Java Script
causes ultimately a call to IDispatch::GetIDsOfNames
and on success
IDispatch::Invoke
is called. The parameters are passed as VARIANT
s and are converted into Any
s and a Sequence
, in
and in/out parameter are treated much differently in the process. The problem
now is how to recognise the kind of the parameter because they are all represented
by IDispatch
objects (VT_DISPATCH
). To solve this
problem every time an IDispatch
is to be converted it is checked
what kind of parameter this parameter is expected to be and the conversion goes
accordingly. For this it is necessary to acquire type information that is usually
provided by the office application. In a remote scenario several network round trips
would be required to deliver those information to the client. To prevent a severe
performance impact it is desirable to dispense with type information. "ValueObjects"
are a means to come close to that requirement.
8.2 The Value Object
A 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 IDispatch
. The uno wrapper queries every IDispatch
parameter for the IJScriptValueObject
interface and if that interface
is being returned than the wrapper has a means to find out the exact type by
using that interface and is not dependent on type information provided by the
office application. ValueObjects and other parameters can be used interchangeably.
8.3 Creation
Every 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 Object
A Value Object is actually an instance of class JScriptValue
that
implements IDispatch
and IJScriptValueObject
. The
IJScriptValueObject
looks like:
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; };
IJScriptValueObject::Set
: Sets
a value and specifies its type. The param "type
"
specifies the type by a string and the parameter "value
"
contains the value. Usable in JavaScript.
IJScriptValueObject::Get:
Delivers
the value that is contained in the ValueObject. Usable in JavaScript.
IJScriptValueObject::InitOutParam:
Stipulates
the ValueObject to represent an out parameter. Usable in JavaScript.
IJScriptValueObject::InitInOutParam:
Stipulates the ValueObject to
represent an in/out parameter. Usable in JavaScript.
IJScriptValueObject::IsOutParam:
Returns a boolean (as out
parameter)indicating whether the ValueObject acts as out parameter or
not. A return value of true means the object is an out parameter. Not
for use in JavaScript.
IJScriptValueObject::IsInOutParam:
Returns a boolean
(as out parameter) indicating whether the ValueObject acts as
in/out parameter or not. A return value of true means the objects is
an in/out parameter. Not for use in JavaScript.
IJScriptVal
vueObject::GetValue:
Returns the type
and value of the ValueObject as out parameter. Not for use in JavaScript. As
seen in the above function descriptions only Get
,
Set
, InitInOutParam
and InitOutParam
can be used in JavaScript. To be more exact these
functions are exposed by IDispatch
.
8.5 Supported Types
When 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).
Name | Type | TypeClass |
---|---|---|
char | sal_Unicode | TypeClass_CHAR |
boolean | sal_Bool | TypeClass_BOOL |
byte | sal_Int8 | TypeClass_BYTE |
short | sal_Int16 | TypeClass_SHORT |
unsigned short | sal_uInt16 | TypeClass_UNSIGNED_SHORT |
long | sal_Int32 | TypeClass_LONG |
unsigned long | sal_uInt32 | TypeClass_UNSIGNED_LONG |
string | OUString | TypeClass_STRING |
float | float | TypeClass_FLOAT |
double | double | TypeClass_DOUBLE |
any | Any | TypeClass_ANY |
object | XInterface | TypeClass_INTERFACE |
Sequences are represented by prepending "[]", e.g. []char, [][]byte, [][][]object, etc.
8.6 Usage
Sequence:
// object is an UNO object var value= object._GetValueObject(); var array= new Array(1,2,3); value.Set("[]short",array); object.function( value);
The array could also contain ValueObjects if necessary:
var value1= object._GetValueObject(); var value2= object._GetValueObject(); value1.Set("short", 100); value2.Set("short", 111); var array= new Array(); array[0]= value1; array[1]= value2; var allValue= object._GetValueObject(); allValue.Set("[]short", array); object.function( allValue);
Out parameter:
// object is an UNO object var value= object._GetValueObject(); value.InitOutParam(); object.functionOut( value); var out= value.Get();
In / out parameter:
// object is an UNO object var value= object._GetValueObject(); value.InitInOutParam("long", 123); object.functionInOut( value); var out= value.Get();
9 Limitations of the dispatch objects
The 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 interfaces
10.1 Intention
It 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 Conventions
When 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 queryInterface
mechanism
does not work for COM components with implemented UNO interface, they have to
supply a means to proclaim those interfaces. This is achieved by the property
"_implementedInterfaces
" that holds an array of strings
which contain the fully qualified interface names. The property is not needed
when only one interface is supported.
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 IDispatch
is that all parameters (meaning the actual arguments to the function
that is called in turn) are converted into the types that the
component expects. Although the OLE Bridge does not just pass
arguments with the type VT_VARIANT |VT_BYREF but instead the actual
type (see mapping table) it is still good practice to convert
parameters within the IDispatch::Invoke implementation.
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 IDispatch
.
UNO Type Class | OLE Automation Type as VARTYPE |
---|---|
INTERFACE | VT_DISPATCH (IDispatch*) |
STRUCT | VT_DISPATCH (IDispatch*) |
ENUM | VT_I4 (long) |
BOOLEAN | VT_BOOL (VARIANT_BOOL) |
STRING | VT_BSTR (BSTR) |
FLOAT | VT_R4 (float) |
DOUBLE | VT_R8 (double) |
CHAR | VT_I2 (short) |
BYTE | VT_UI1 (unsigned char) |
SHORT | VT_I2 (short) |
UNSIGNED_SHORT | VT_I2 (short) |
LONG | VT_I4 (long) |
UNSIGNED_LONG | VT_I4 (long) |
SEQUENCE | VT_ARRAY | VT_VARIANT |
ANY | VT_VARIANT |
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 JScript
In 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 "_implementedInterfaces
".
Additionally a JScript has to support the property "_environment"
which must have the value "JScript".
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() { ... }
Author:
Joachim Lingner
($Date: 2004/11/27 13:07:07 $) Copyright 2001 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA. |