Sunteți pe pagina 1din 3

How to use OLE Automation to automate OpenOffice OLE objects

OpenOffice.org provides OLE server functionality that allows to embed its documents as OLE objects into OLE client applications. While OpenOffice 1.x only supported the OLE1 protocol ( so alled outplace activation), OpenOffice 2.0 also supports the OLE2 protocol (so called inplace activation). In both cases the embedded object supports the IDispatch interface that allows the access the whole API of the embedded document. This is the late binding way to script COM objects, also called OLE Automation. Of course the object does not need to be activated for this, it is enough to have the object in the running state, at least as long as only APIs are used that do not require an existing view. Modifications done at the object while it is not active of course require to update the cached visual representation from the client side. The OpenOffice.org developers guide contains general information how OLE automation can be used to script OpenOffice.org documents (chapter 3.4.4). It also contains a VB program that shows how to access the OpenOffice.org API through this bridge. For C++ the effort is a little bit higher. In addition to what you can find in the Developers Guide here are some helpful tips and functions that make it easier to work with C++ and the OLE Automation bridge of OpenOffice.org. The examples use the ATL from Microsoft, but only very sparingly. All access to the OpenOffice.org API starts with a first interface pointer. In the case of OLE embedding it is retrieved from the embedded object, in the general case of OLE automation with OpenOffice.org the first interface pointer will be the one from the UNO Service Manager (see below). In the COM world this interface will be IDispatch, in the UNO world it will be any object with its interfaces. If the OpenOffice.org API reference is used to check the possible interfaces and properties of an object that is represented by an IDispatch interface, it is necessary to understand how this interacts with the OLE Automation bridge. The IDispatch based access to the object doesn't know about interfaces, it sees the object as a collection of method calls, contributed by all of its interfaces. This is the same way how the built-in StarBasic sees a UNO object. All these method calls need to be done as invoke calls of the IDispatch interface. The following function comes in handy here:
HRESULT ExecuteFunc( IDispatch* idispUnoObject, OLECHAR* sFuncName, CComVariant* params, unsigned int count, CComVariant* pResult ) { if( !idispUnoObject ) return E_FAIL; DISPID id; HRESULT hr = idispUnoObject->GetIDsOfNames( IID_NULL, &sFuncName, 1, LOCALE_USER_DEFAULT, &id); if( !SUCCEEDED( hr ) ) return hr; DISPPARAMS dispparams= { params, 0, count, 0}; // DEBUG EXCEPINFO myInfo; return idispUnoObject->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, pResult, &myInfo, 0); }

It gets the IDispatch interface of the callee as input parameter together with the name of the desired method and its parameters and provides the return value of the API call.

The latter is represented by a CComVariant*, and all parameters of the call are passed in a CComVariant array in the reverse order, that means that the first element of the arrray contains the last parameter of the UNO interface method etc. It's absolutely necessary to set the write count! The return values can be simple data types, structured data types, interface return types or arrays of such types. If the return value is an interface type or a structured data type (see below), it is again represented by an IDispatch, so another useful helper function that immediately returns an IDispatch as a result of the call is quite useful:
HRESULT GetIDispByFunc( IDispatch* idispUnoObject, OLECHAR* sFuncName, CComVariant* params, unsigned int count, CComPtr<IDispatch>& pdispResult ) { if( !idispUnoObject ) return E_FAIL; CComVariant result; HRESULT hr = ExecuteFunc( idispUnoObject, sFuncName, params, count, &result ); if( !SUCCEEDED( hr ) ) return hr; if( result.vt != VT_DISPATCH || result.pdispVal == NULL ) return E_FAIL; pdispResult = CComPtr<IDispatch>( result.pdispVal ); } return S_OK;

Sometimes the UNO service manager shall be used to create some UNO services from OpenOffice.org. This service manager is available as a COM object itself that can be used through IDispatch:
CComPtr<IDispatch> pDispFactory; CLSID clsFactory = {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}}; HRESULT hr = CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pDispFactory);

At times structured data types have to be used, f.e. as input parameters for a method call. In general a structured data type is created by using a special service from the bridge itself (pDispFactory is the global Service Manager from the above example).
HRESULT GetUnoStruct( OLECHAR* sStructName, CComPtr<IDispatch>& pdispResult ) { return GetIDispByFunc( pDispFactory, L"Bridge_GetStruct", &CComVariant( sStructName ), 1, pdispResult ); }

This represents the structured data type as an IDispatch also. Here's an example how a com.sun.star.awt.Rectangle object is created and filled with data:
// create rectangle structure CComPtr<IDispatch> pdispRectangle; HRESULT hr = GetUnoStruct( L"com.sun.star.awt.Rectangle", pdispRectangle ); if( !SUCCEEDED( hr ) ) return hr; OLECHAR* sRectMemberNames[4] = { L"X", L"Y", L"Width", L"Height" }; CComVariant pRectVariant[4]; pRectVariant[0] = pRectVariant[1] = pRectVariant[2] = pRectVariant[3] = CComVariant( 0 ); hr = PutPropertiesToIDisp( pdispRectangle, sRectMemberNames, pRectVariant, 4 ); if( !SUCCEEDED( hr ) ) return hr;

It uses another helper function that fills the struct with data:
HRESULT PutPropertiesToIDisp( IDispatch* pdispObject, OLECHAR** sMemberNames, CComVariant* pVariant, unsigned int count ) { for( unsigned int ind = 0; ind < count; ind++ ) { DISPID id; HRESULT hr = pdispObject->GetIDsOfNames( IID_NULL, &sMemberNames[ind], 1, LOCALE_USER_DEFAULT, &id ); if( !SUCCEEDED( hr ) ) return hr; hr = CComDispatchDriver::PutProperty( pdispObject, id, &pVariant[ind] ); if( !SUCCEEDED( hr ) ) return hr; } } return S_OK;

On the other hand structured data types as return values are also quite common, and from the above said it follows that they again are represented as an IDispatch. The members of the struct behind the IDispatch can be retrieve by a function that is very similar to the last one:
HRESULT GetPropertiesFromIDisp( IDispatch* pdispObject, OLECHAR** sMemberNames, CComVariant* pVariant, unsigned int count ) { for( unsigned int ind = 0; ind < count; ind++ ) { DISPID id; HRESULT hr = pdispObject->GetIDsOfNames( IID_NULL, &sMemberNames[ind], 1, LOCALE_USER_DEFAULT, &id ); if( !SUCCEEDED( hr ) ) return hr; hr = CComDispatchDriver::GetProperty( pdispObject, id, &pVariant[ind] ); if( !SUCCEEDED( hr ) ) return hr; } } return S_OK;