In
Part 1,
we studied the fundamental concepts of OpenOffice.org's software
development kit (SDK) and how the SDK can be used to communicate with
the OOo programs. We now are ready to write an application. As
previously stated, we are going to develop a program that is able to
interact with OpenOffice.org's spreadsheet application, Calc. Two
reasons are behind this choice. First, solving the problems raised in
creating this program will acquaint us with many of the most important
aspects of UNO (Universal Network Objects) programming. Second, spreadsheets allow users to build
nice reports easily. If we are able to control a spreadsheet
application, it can be turned into our personal report generator.
The code that will allow our application to work with Calc can be divided into six
sections, each one with its own task:
- Connecting to OpenOffice.org
- Opening the document
- Choosing a worksheet
- Modifying the chosen worksheet
- Printing the worksheet
- Closing the connection
We are going to study the complete source code of a simple program that,
regarding the first two items, largely depends on the example located in
<OpenOffice.org SDK directory>/examples/DocumentLoader. A Qt based application
also is available.
In order to make this article more readable, I refer
to seven short code snippets, one more than the above numbered points.
For the sake of clarity, I decided to write the numerous #includes
and using namespace directives in a separate section, Listing 1.
Listing 1. Header Files and Using
Namespace
#include <stdio.h> #include <cppuhelper/bootstrap.hxx> #include <osl/file.hxx> #include <osl/process.h> #include <com/sun/star/frame/XDesktop.hpp> #include <com/sun/star/bridge/XUnoUrlResolver.hpp> #include <com/sun/star/frame/XComponentLoader.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/sheet/XSpreadsheetDocument.hpp> #include <com/sun/star/sheet/XSpreadsheets.hpp> #include <com/sun/star/sheet/XSpreadsheet.hpp> #include <com/sun/star/table/XCell.hpp> #include <com/sun/star/table/XCellRange.hpp> #include <com/sun/star/container/XIndexAccess.hpp> #include <com/sun/star/view/XPrintable.hpp> #include <com/sun/star/view/PaperOrientation.hpp> #include <string.h> using namespace rtl; using namespace cppu; using namespace com::sun::star::view; using namespace com::sun::star::table; using namespace com::sun::star::container; using namespace com::sun::star::sheet; using namespace com::sun::star::uno; using namespace com::sun::star::lang; using namespace com::sun::star::beans; using namespace com::sun::star::bridge; using namespace com::sun::star::frame; using namespace com::sun::star::registry;
Every interface--and we use a lot of them--is declared in its own header
file, and every service adds its own namespace. The first point, the
header file, needs the com.sun.star.bridge.UnoUrlResolver service. Let us
study its name, as that can provide us with some interesting information. First of all,
the influence of Java is unquestionable: the dot notation and the
names--com, sun--show this clearly. And it's no wonder, as Sun is
involved in the OpenOffice.org project. Therefore, every C++ software instrument is an adaptation of
its Java counterpart. For instance, nested namespace directives
replace the Java packages notation. Thus, when we read the name of
a service, we can benefit by keeping in mind the typical Java packages
structure. Doing so makes the reference guide, <OpenOffice.org SDK
directory>/docs/common/ref/com/module-ix.html, much easier to use.
What can we say about the service we need? After com.sun.star, the
common root, comes "bridge". This refers to the interprocess bridge we
spoke about in the previous article, used to establish the connection with the server. If we want to connect our
application to this server, we must find it; in other words, we have to
resolve its URL. This is the task of the UnoUrlResolver service. But
remember, services can do nothing on their own. They simply describe what must be
done, not how it can be done. We do not create the service, but an
interface to the service. From a programmer's point-of-view, services are
above all a conceptual help. But, the source code only has interfaces. Invoking them,
however, could cause us some problems, because we must specify their
positions within the nested namespace structure. This way, our code
hardly would be readable. That's why we wrote the code in
Listing 1; adding the using namespace directives makes our source shorter.
Listing 2. Connecting to the OpenOffice.org Server
//create a OUString to connect to the local host
OUString sConnectionString=OUString::createFromAscii
("uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager");
//create an instance of XSimpleRegistry...
Reference<XSimpleRegistry> xSimpleRegistry(createSimpleRegistry());
// ... and connect it to the registry file: prova.rdb, current directory
OUString sRdbFile=OUString::createFromAscii("./prova.rdb");
xSimpleRegistry->open(sRdbFile,sal_True,sal_False);
//open an XComponentContext based on the registry file in
// xSimpleRegistry
Reference<XComponentContext> xComponentContext
(bootstrap_InitialComponentContext(xSimpleRegistry));
//build the client-side service factory
Reference<XMultiComponentFactory> xMultiComponentFactoryClient
(xComponentContext->getServiceManager());
//create an XInterface of the service factory, relying on the service string
//and on the context; then...
OUString sService= OUString::createFromAscii
( "com.sun.star.bridge.UnoUrlResolver" );
Reference<XInterface> xInterface=
xMultiComponentFactoryClient->createInstanceWithContext
(sService,xComponentContext);
//...query it to build an XUnoUrlResolver interface and...
Reference<XUnoUrlResolver> resolver(xInterface,UNO_QUERY);
//...use its resolve() method to reach the openOffice server and to
//create an XInterface to the server
try
{
xInterface=Reference<XInterface>(resolver->resolve(sConnectionString),UNO_QUERY);
}
catch(...)
{
printf("Error: could not connect to the server");
exit(1);
}
Listing 1 shows us how to proceed. The data to establish the connection
is stored in a Unicode character string, a type defined as OUString in
UNO. Tthe target is on the local host and uses port 8100. The key
element in this excerpt of code is the Reference template, which allows us
to create C++ usable data, starting from abstract definitions of services.
We here see how to use it to create the service we need. Before using the service
factory, however, we must specify the context within which our service will
work. That is, the program has to know where to find the registry file we
previously talked about, the one that contains the binary description of
every data type and service we are going to use.
Opening rdb files is the task of the interface XSimpleRegistry. For
the first time, we see the use of the template Reference to instantiate
interfaces. In this case, the constructor calls the function
createSimpleRegistry to accomplish its work. Once we have the object
xSimpleRegistry, its open method allows access to prova.rdb, stored
in the current directory. The parameters passed to open tidily specify
the name of the rdb file, if this file must be opened in read-only mode
and if it must be created if it does not exist. By the way, we create the
OUString object we need by using the static method createFromAscii.
The following step binds the data and services stored in the registry
file to an interface XComponentContext. Notice the function
bootstrap_InitialComponentContext; it "bootstraps an initial component
context with service manager upon a given registry" (see the C++
Reference Guide, <OpenOffice.org SDK directory>/docs/cpp/ref/index.html).
As we have a context, we can use a service manager to implement
UnoUrlResolver. Of course, what we really need is a service manager
interface--XMultiComponentFactory, created by getServiceManager(),
a method of XComponentContext. Then, we build an XInterface
referring to the service--XInterface is the ancestor class of
every interface--calling the method createInstanceWithContext() of
XMultiComponentFactory. createInstanceWithContext() has two parameters,
the name of the service and the context. XInterface is only a proxy
of the service, and we can't use it directly. We instead must use the
interface exported by the service. XUnoUrlResolver, whose resolve()
method is able to connect to servers, but, at least under Linux, it only
works if OpenOffice.org already is running. The following line shows how
to create an XUnoUrlResolver interface, resolver, starting from an
XInterface (xInterface):
Reference<XUnoUrlResolver> resolver(xInterface,UNO_QUERY);
As previously stated, XInterface refers to the UnoUrlResolver. We query
this interface asking it for an XUnoUrlResolver using the Reference
template, and as we are going to see, the process is always the same. All
we have to do is to invoke Reference by passing two parameters, a reference
to the interface we are querying (XInterface, in our example) and the
UNO_QUERY enumerated constant. Obviously, the syntax used depends on
the programming language depending. That's why the Developer Guide,
which particularly refers to Java, gives us different information.
The final lines of Listing 2 try to connect to the server. If the resolve()
method succeeds, it returns a server proxy. The code shows how to use
this proxy to build an XInterface of the service which is listening on
the server. The technique is the same as what we have just seen. The try-catch
block allows us to safely close the program should something go wrong.
Opening the document requires the com.sun.star.frame.Desktop service,
which is the environment for all the components we can instantiate
from frames. The code in Listing 3 largely relies on the techniques we
just learned and uses the last interface we created in Listing 2.
Listing 3. Opening the Document
//acquire the server-side context
Reference<XPropertySet> xPropSet( xInterface, UNO_QUERY );
OUString sProperty=OUString::createFromAscii("DefaultContext");
xPropSet->getPropertyValue(sProperty) >>= xComponentContext;
//take the OpenOffice service manager
Reference<XMultiComponentFactory> xMultiComponentFactoryServer
(xComponentContext->getServiceManager());
//instantiate the component supporting the desired service
//(load a document)
sService=OUString::createFromAscii("com.sun.star.frame.Desktop");
xInterface=xMultiComponentFactoryServer->createInstanceWithContext
(sService,xComponentContext);
Reference<XComponentLoader>xComponentLoader
(xInterface,UNO_QUERY);
//declare 3 OUString, to store URL, working directory
//and name of the document
OUString sDocUrl, sWorkingDir,sName;
//find the working directory and write it
osl_getProcessWorkingDir(&sWorkingDir.pData);
//write the name of the document
sName= OUString::createFromAscii("./prova.sxc");
//create the URL
osl::FileBase::getAbsoluteFileURL( sWorkingDir,sName,sDocUrl);
//set the document type
OUString sType=OUString::createFromAscii("_blank");
//try to open the document
Reference<XComponent> xComponent=
xComponentLoader->loadComponentFromURL
(sDocUrl,sType,0,Sequence<PropertyValue>());
That XInterface is used to find the new context where com.sun.star.frame.Desktop
works. In fact, the old context refers to the environment where the client
process works. Generally speaking, the server could work in a different
environment and in a different host. That's why we use different names
for the service managers operating in the client context and in the
server one. The new context is a property of the proxy we acquired in
the previous point. In order to find the properties of a service, we
can query it the usual way and then put the results in an interface,
XPropertySet. The following line is an excerpt from Listing 3. It shows
how to extract the data from an interface called xPropSet and put it in
xComponentContext, the new context interface:
xPropSet->getPropertyValue(sProperty) >>= xComponentContext;
When we defined properties, we said that they are identified
by a name-value pair. In our example, we pass the name to the
getPropertyValue() method in the sProperty string, and it returns the
value. This method is the only one to access properties. Therefore, it
must be able to return many types of data, as there are many types of
properties. That's why the data type Any and the extraction operator
>>= have been defined. Starting from this point, we use the same
methodologies that we used in Listing 2.
The service exports three interfaces. We need XComponentLoader, whose method
loadComponentFromURL(), in cases of success, returns an XComponent that manages and frees the
resources related to the document. The first parameter of the method is
the string that contains the URL of the file we want to open. Notice
that passing the relative path of the file we want to open could
not work, because if the server worked on a remote host, it would
not find the document.
Our program processes the file prova.sxc, which is stored
in the current directory, from the application point of view. The
osl_getProcessWorkingDir() function finds the process working directory,
while osl::FileBase::getAbsoluteFileURL() builds the URL of the
document. The last parameter of loadComponentFromURL() is a sequence,
created by the Sequence template. Sequences are sets of different kinds
of variables, and the number of their components is not fixed. We don't
need setPropertyValue and getPropertyValue to work with them. Accessing
sequences is quite similar to accessing arrays, because we have to use an
index. The sequence we are interested in stores property values,
PropertyValue. I want to remark that this is the last time we use
service factories, because all services and interfaces we are going to
implement are in the same branch of the UNO taxonomy.
The procedure we are describing, until now, does not depend on the kind
of document we want to open. Therefore, we can use it when working
with texts, presentations and so on. Starting here, however, we have
to introduce some instruments that have been designed specifically for
spreadsheets. OpenOffice.org spreadsheets contain collections of
worksheets. We have to choose and then open one of these worksheets in order
to modify and print it. The UNO interface system allows us to reach
our goal, and Listing 4 shows us how to proceed.
Listing 4. Accessing the Worksheet
//create xSheetDocument Reference<XSpreadsheetDocument> xSheetDocument (xComponent,UNO_QUERY); //create an instance of XSpreadsheets, which is a worksheets collection Reference<XSpreadsheets> xSheets=xSheetDocument->getSheets(); //create a class to interact with single worksheets; //the single worksheets are referenced by an XIndexAccess interface Reference<XIndexAccess> xIndex (xSheets,UNO_QUERY); //take the first worksheet (index=0)... Any any=xIndex->getByIndex(0); //...then create an instance of Xspreadsheet, able to manage //single worksheets; Reference<XSpreadsheet> xSheet; //finally, assign the first worksheet to xSheet any >>= xSheet;
First of all, we query our XComponent to create the interface
XSpreadsheetDocument, whose getSheets() method returns XSpreadheets.
The task of the latter is to manage collections
of worksheets. We then need XSpreadsheet, which controls a single
worksheet. XSpreadheets contains one XSpreadheet for each worksheet
in the document. Each XSpreadheet is identified by its index, as with
an array element. Choosing the right XSpreadheet requires the creation
of the XIndexAccess container. Containers are data structures
similar to sequences. All we have to do is pass the index of the
worksheet we want to work with to the method getByIndex() of our
container. Naturally, getByIndex() returns an Any data type, thus
we have to use the operator >>=.
From XSpreadsheet we create an XCellRange, as shown in Listing 5.
Listing 5. Modifying the Chosen Worksheet
//create an XCellRange interface to interact with cells ranges
Reference<XCellRange> xCellRange(xSheet,UNO_QUERY);
//create an XCell interface to interact with single cells
Reference<XCell> xCell;
//take the cell located in column 5, row 6
int column=5;
int row=6;
xCell=xCellRange->getCellByPosition(column,row);
//write a number in the cell
xCell->setValue(1960);
//change cell
column=6;
row=5;
xCell=xCellRange->getCellByPosition(column,row);
//write a string in the cell
sString=OUString::createFromAscii('stringa');
xCell->setFormula(sString);
XCellRange is able to manage a range of cells, and its getCellByPosition()
method finally returns an XCell interface. XCell has two methods of
writing data in a cell, setValue() for numbers and setFormula() for
anything else.
The printing procedure, shown in Listing 6, is based on XPrintable and
starts from XSheetDocument (see Listing 6).
Listing 6. Printing the Worksheet
//open an XPrintable interface, linked to xSheetDocument
Reference<XPrintable> xPrintable(xSheetDocument,UNO_QUERY);
//load the printer setting
Sequence<PropertyValue> pPrinter=xPrintable->getPrinter();
//try to modify the PaperOrientation property
OUString orient=OUString::createFromAscii("PaperOrientation");
int k=0;
//check the names until PaperOrientation
do
{
if(orient.compareTo(pPrinter[k].Name)==0)
{
pPrinter[k].Value <<= PaperOrientation_LANDSCAPE;
}
k++;
}
while(orient.compareTo(pPrinter[k].Name)!=0);
//assign properties to the printer
xPrintable->setPrinter(pPrinter);
//print
xPrintable->print(pPrinter);
This interface stores the printer configuration in its
sequence of properties. The code modifies the paper orientation property,
whose name is PaperOrientation. We meet here another extraction operator,
<<=, defined to assign an Any value to a variable.
The print() method starts the printing process. It is an asynchronous
process, therefore its execution could take a little while.
Finally, Listing 7 shows the code to close the document and the
connection to the server.
Listing 7. Closing the Connection
//try to close xComponent, until dispose() succeeds
char b=0;
while(b==0)
{
try
{
xComponent->dispose();
}
catch(...)
{
b=1;
}
}
//delete the client-side service factory
Reference<XComponent>
::query(xMultiComponentFactoryClient)->dispose();
We must delete the XComponent we got when loading prova.sxc as well as the
client-side service factory, in this order. The key-method is dispose,
but it is not defined for XMultiComponentFactory interfaces. The following
line solves the problem.
Reference<XComponent> ::query(xMultiComponentFactoryClient)->dispose();
We start from an instance of XMultiComponentFactory called
XMultiComponentFactoryClient. The query() function tries to build an
XComponent interface based on XMultiComponentFactoryClient. If the
operation succeeds, dispose is invoked. Please, do not delete the
server-side service factory unless you want to close the OpenOffice.org
server.
Notice that dispose fails if printing processes are in progress. This
creates a problem, though, because the method returns nothing.
Therefore, we are not able to control whether it works. Listing 7
provides a possible solution, though: call dispose within a
try-catch block until it succeeds.
Writing the Makefile
We have to know where the header files and the libraries we need are
stored, in order to give all the necessary information to the compiler and
linker. Let's begin with the header files. They are stored in the
following directories:
- <OpenOffice sdk directory>/include: starting from
this directory, we can find general-purpose data and functions
declarations, including semaphores and threads. - Regarding the interfaces and services we use, we create their header
files with cppumaker as we need them. In other words, we choose where
to store them. For example, we could write the header files directory
tree starting from the source code directory.
We have to link the following dynamic libraries: libstlport_gcc.so,
usually written in /usr/lib during the installation process of stlport;
libsal.so, libsalhelpergcc3.so, libcppu.so and libcppuhelpergcc3.so, all
stored in <OpenOffice directory>/program. What are these libraries for? We
could say that libsal.so is the base of the whole UNO system (see Figure
1); sal is short for system abstraction layer. This library
contains some useful runtime functions, but it does not refer to any UNO
component. libsalhelpergcc3.so helps libsal.so. libcppu.so (C++ UNO) is the
core of the C++ system of libraries. Its functions create interfaces,
manage bridges and so on. Finally, libcppuhelpergcc3.so manages the bootstrap
of UNO.
Figure 1. Overview of the Base Shared Libraries
(C++)
One more word about libraries: people using the 3.3.x releases of GCC
have to replace the file libgcc_s.so.1 in the OpenOffice.org directory
with the one shipped with their compiler. Otherwise, the exception
handling causes a segmentation fault at runtime. The issue is thoroughly
treated by Stephan Bergmann
here.
Finally, some flags and options are available for the compiler and linker.
The following lines show how to compile test1.cpp to build the executable
test1, assuming that the header files tree written by cppumaker starts from
the source directory:
gcc -c -O -fpic -fno-rtti -I. -I/usr/include -I/$(OO_SDK_HOME)/include -DUNX -DGCC -DLINUX -DCPPU_ENV=gcc3 -o test1.o test1.cpp gcc -Wl -export-dynamic -L/OOSDK/linux/lib -L/$(OFFICE_HOME)/program -o test1 test1.o -lcppuhelpergcc3 -lcppu -lsalhelpergcc3 -lsal -lstlport_gcc.
Trending Topics
| You Need A Budget | Feb 10, 2012 |
| The Linux powered LAN Gaming House | Feb 08, 2012 |
| Creating a vDSO: the Colonel's Other Chicken | Feb 06, 2012 |
| Your CMS Is Not Your Web Site | Feb 01, 2012 |
| Casper, the Friendly (and Persistent) Ghost | Jan 31, 2012 |
| Razor-qt 0.4 - Qt based Desktop Environment | Jan 30, 2012 |
- Fun with ethtool
- Linux-Based X Terminals with XDMCP
- 100% disappointed with the decision to go all digital.
- Readers' Choice Awards 2011
- Parallel Programming with NVIDIA CUDA
- You Need A Budget
- Validate an E-Mail Address with PHP, the Right Way
- The Linux powered LAN Gaming House
- The Linux RAID-1, 4, 5 Code
- Python for Android
- Gnome3 is such a POS. No one
5 hours 4 min ago - Gnome 3 is the biggest POS
5 hours 14 min ago - I didn't knew this thing by
11 hours 19 min ago - Author's reply
14 hours 43 min ago - Link to modlys
15 hours 50 min ago - I use YNAB because of the
16 hours 1 min ago - Search
21 hours 4 min ago - Question
21 hours 27 min ago - for the record
21 hours 30 min ago - That's disappointing. Thanks
23 hours 53 min ago





Comments
program to control openoffice
Hi
I can not download code. At the same time, i can not make the example. How I can use Qt to control the openoffice ?
thanks!
Where do i find a Java Tutorial?
Hey where do i find a java tutorial for start writing code for openoffice and java?
qt application
You can download the source code of the Qt application mentioned in part 2 at the following address:
http://www.saena.it/riso/progetto.tar
Franco Pingiori
Again a very interesting
Again a very interesting article. I have spent one year writing a C++/UNO document (starting as beginner) and many subjects are not documented (often because of knowledge lack) but are tackled here : for instance how to close a OOo connection and many others.
Thank you again for this article.
A HTML version of my document is now available :
Free UNO/C++ Documentation
See also the OOoWiki :
See also the OOoWiki :
using C++ with SDK