2021-12-05

67: Connection-Aware Possibly Remote C++ External UNO Client

<The previous article in this series | The table of contents of this series | The next article in this series>

To incorporate the LibreOffice/OpenOffice functionality into your independent (non-macro) program, possibly a long-standing one like a Web application

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: C++

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to connect his or her external C++ program to any UNO server (usually a LibreOffice or Apache OpenOffice instance), which can be in a remote computer, in a connection-aware way.

Orientation


There are an article on how to create a LibreOffice or Apache OpenOffice daemon and an article on how to create a LibreOffice or Apache OpenOffice Microsoft Windows service.

There is an article on optimally using LibreOffice or Apache OpenOffice as a files converter.

There is an article on executing any UNO dispatch command and getting all the information of the execution in C++.

There is an article on creating any UNO component in C++.

There are some articles on creating connection-aware external UNO clients in some other programming languages (Java, C# and Python).


Main Body

Stage Direction
Here are Special-Student-7, Objector 11A, and Objector 11B in front of a computer.


1: The Clarification of the Goal


Special-Student-7
The goal of this article is to connect our independent C+ program to any UNO server, in a scrupulous way.

"UNO server" is usually a LibreOffice or Apache OpenOffice instance, and "independent" means that the program is not any macro inside the LibreOffice or Apache OpenOffice instance.

Objector 11A
Does "scrupulous" mean "Connection-Aware"?

Special-Student-7
Being connection-aware is a highlight, although that is not all, sir.

Objector 11A
What else are there?

Special-Student-7
The most pervasively promoted way is a lazy way, which insists that the UNO server should be a LibreOffice or Apache OpenOffice instance in the local computer and gratuitously starts a LibreOffice or Apache OpenOffice instance in the local computer, before connecting to the local LibreOffice or Apache OpenOffice instance.

Being scrupulous means that our way is not that lazy way.

Objector 11A
The "lazy way" sounds good! It kindly starts a LibreOffice or Apache OpenOffice instance for me?

Special-Student-7
Well, aside from the disgusting intrusion into my intentions, the UNO server may not be a local LibreOffice or Apache OpenOffice instance.

Objector 11A
Then, what may it be?

Special-Student-7
My program may be a Web application in an application server or a GUI program in client computers while the UNO server may be in a data server, a situation in which connection-awareness can be handy.

Objector 11B
What does "connection-aware" mean exactly? I mean, the program would be "connection-aware" anyway, because the program would know the severance of the connection when using the connection inevitable fails.

Special-Student-7
Madam, my sense of being connection-aware is that the connection severance is detected promptly and orderly.

In your sense of connection-awareness, users of the client program would input a lot of data on the screen, push a button, find the connection have been severed, and think "The connection has been severed? Since when? Inform me earlier so that I did not need to input that data futilely!".

Objector 11B
. . . What do you mean by "orderly"?

Special-Student-7
Rather than having each module detect and deal with severances by failing, having a single events listener centrally detect and deal with severances is an orderly way for me.

Objector 11B
Supposing that the program has detected the severance, can it reestablish a connection automatically?

Special-Student-7
It can try to reestablish a connection, but the cause of the problem has to be removed first of course, in order for the try to succeed.

For example, if the demise of the UNO server is the cause, a new UNO server may be able to be started automatically, but if someone has pulled a cable off, the cable will have to be replaced by hand.

Objector 11B
But once the environment is restored, will a connection be reestablished immediately?

Special-Student-7
We will see such a sample program.

The UNO server may be or not be a LibreOffice or Apache OpenOffice instance, but if it is, please note that the LibreOffice or Apache OpenOffice instance has to be made a UNO server, in order for it to be a UNO server.

The UNO server can be in a remote computer, but in that case, the UNO server has to use the TCP/IP socket protocol.

Objector 11B
What do you mean by "The UNO server may ~ not be a LibreOffice or Apache OpenOffice instance"? What else may it be?

Special-Student-7
In fact, you can create your own UNO server, rather easily.


2: Some Notes


Special-Student-7
It is supposed that a proper development environment is ready (there is an article for Linux or for Microsoft Windows).

Specifically note that you probably have to use Visual C++ in Microsoft Windows, which is because UNO C++ libraries have been built with Visual C++.


3: The Code


Special-Student-7
Without further ado, this is the code.

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.hpp

@C++ Source Code
#ifndef __theBiasPlanet_unoUtilitiesTests_connectingFromExternalProgramTest1_Test1Test_hpp__
	#define __theBiasPlanet_unoUtilitiesTests_connectingFromExternalProgramTest1_Test1Test_hpp__
	
	#include <condition_variable>
	#include <mutex>
	#include <cppuhelper/compbase1.hxx>
	#include <com/sun/star/bridge/XBridge.hpp>
	#include <com/sun/star/bridge/XInstanceProvider.hpp>
	#include <com/sun/star/lang/XEventListener.hpp>
	#include <com/sun/star/uno/Reference.hxx>
	#include <com/sun/star/uno/XComponentContext.hpp>
	#include <rtl/ustring.hxx>
	
	using namespace ::std;
	using namespace ::com::sun::star::bridge;
	using namespace ::com::sun::star::lang;
	using namespace ::com::sun::star::uno;
	using namespace ::cppu;
	using namespace ::rtl;
	
	namespace theBiasPlanet {
		namespace unoUtilitiesTests {
			namespace connectingFromExternalProgramTest1 {
				class Test1Test {
					public:
						class InitialUnoObjectsProvider: public WeakImplHelper1 <XInstanceProvider> {
							private:
								Reference <XComponentContext> & i_localUnoObjectsContext;
							public:
								InitialUnoObjectsProvider (Reference <XComponentContext> & a_localUnoObjectsContext);
								virtual ~InitialUnoObjectsProvider ();
								virtual Reference <XInterface> SAL_CALL getInstance (OUString const & a_initialUnoObjectName) override;
						};
						class UnoConnectionEventsListener: public WeakImplHelper1 <XEventListener> {
							public:
								UnoConnectionEventsListener ();
								~UnoConnectionEventsListener ();
								virtual void SAL_CALL disposing (EventObject const & a_event) override;
						};
						class UnoConnectionInformation {
							private:
								Reference <XBridge> i_unoBridge;
								Reference <XComponentContext> i_remoteUnoObjectsContext;
							public:
								UnoConnectionInformation ();
								~UnoConnectionInformation ();
								bool isEmpty ();
								Reference <XBridge> & getUnoBridge ();
								Reference <XComponentContext> & getRemoteUnoObjectsContext ();
								void setUnoBridge (Reference <XBridge> const & a_unoBridge);
								void setRemoteUnoObjectsContext (Reference <XComponentContext> const & a_remoteUnoObjectsContext);
								void clear ();
						};
						static recursive_mutex s_unoConnectionMaintainingThreadsMutex;
						static condition_variable_any s_unoConnectionMaintainingThreadsCondition;
						static recursive_mutex s_unoConnectionUsingThreadsMutex;
						static condition_variable_any s_unoConnectionUsingThreadsCondition;
						static UnoConnectionInformation s_unoConnectionInformation;
						static int main (int const & a_argumentsNumber, char const * const a_argumentsArray []);
				};
			}
		}
	}
#endif

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.cpp

@C++ Source Code
#include "theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.hpp"
#include <chrono>
#include <iostream>
#include <list>
#include <string>
#include <thread>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/bridge/BridgeExistsException.hpp>
#include <com/sun/star/bridge/XBridgeFactory.hpp>
#include <com/sun/star/connection/NoConnectException.hpp>
#include <com/sun/star/connection/XConnector.hpp>
#include <com/sun/star/frame/XDesktop.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include "theBiasPlanet/coreUtilities/inputs/HaltableStandardInputReader.hpp"
#include "theBiasPlanet/unoUtilities/stringsHandling/UnoExtendedStringHandler.hpp"

using namespace ::std::chrono;
using namespace ::com::sun::star::connection;
using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::lang;
using namespace ::theBiasPlanet::coreUtilities::inputs;
using namespace ::theBiasPlanet::unoUtilities::stringsHandling;

namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace connectingFromExternalProgramTest1 {
			Test1Test::InitialUnoObjectsProvider::InitialUnoObjectsProvider (Reference <XComponentContext> & a_localUnoObjectsContext): i_localUnoObjectsContext (a_localUnoObjectsContext) {
			}
			
			Test1Test::InitialUnoObjectsProvider::~InitialUnoObjectsProvider () {
			}
			
			Reference <XInterface> SAL_CALL Test1Test::InitialUnoObjectsProvider::getInstance (OUString const & a_initialUnoObjectName) {
				if (string ("theBiasPlanet.UnoObjectsContext") == UnoExtendedStringHandler::getString (a_initialUnoObjectName)) {
					return i_localUnoObjectsContext;
				}
				else {
					return Reference <XInterface> ();
				}
			}
			
			Test1Test::UnoConnectionEventsListener::UnoConnectionEventsListener () {
			}
			
			Test1Test::UnoConnectionEventsListener::~UnoConnectionEventsListener () {
			}
			
			void SAL_CALL Test1Test::UnoConnectionEventsListener::disposing (EventObject const & a_event) {
				cout << string ("### The UNO connection has been severed.") << endl << flush;
				unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
				s_unoConnectionInformation.clear ();
				s_unoConnectionMaintainingThreadsCondition.notify_all ();
			}
			
			Test1Test::UnoConnectionInformation::UnoConnectionInformation () {
			}
			
			Test1Test::UnoConnectionInformation::~UnoConnectionInformation () {
			}
			
			bool Test1Test::UnoConnectionInformation::isEmpty () {
				return ! (i_remoteUnoObjectsContext.is ());
			}
			
			Reference <XBridge> & Test1Test::UnoConnectionInformation::getUnoBridge  () {
				return i_unoBridge;
			}
			
			Reference <XComponentContext> & Test1Test::UnoConnectionInformation::getRemoteUnoObjectsContext () {
				return i_remoteUnoObjectsContext;
			}
			
			void Test1Test::UnoConnectionInformation::setUnoBridge (Reference <XBridge> const & a_unoBridge) {
				i_unoBridge = a_unoBridge;
			}
			
			void Test1Test::UnoConnectionInformation::setRemoteUnoObjectsContext (Reference <XComponentContext> const & a_remoteUnoObjectsContext) {
				i_remoteUnoObjectsContext = a_remoteUnoObjectsContext;
			}
			
			void Test1Test::UnoConnectionInformation::clear () {
				i_unoBridge.clear ();
				i_remoteUnoObjectsContext.clear ();
			}
			
			int Test1Test::main (int const & a_argumentsNumber, char const * const a_argumentsArray []) {
				int l_resultStatus = -1;
				try {
					if (a_argumentsNumber != 3) {
						throw runtime_error (string ("The arguments have to be these.\nThe argument 1: the server URL like 'socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext'"));
					}
					string l_unoServerUrl = a_argumentsArray [2];
					list <string> l_unoServerUrlTokens;
					stringstream l_unoServerUrlStringStream (l_unoServerUrl);
					string l_stringToken;
					while (true) {
						if (getline (l_unoServerUrlStringStream, l_stringToken, ';')) {
							l_unoServerUrlTokens.push_back (l_stringToken);
							if (l_unoServerUrlStringStream.eof ()) {
								break;
							}
						}
					}
					if (l_unoServerUrlTokens.size () < 3) {
						throw runtime_error (string ("The server URL has to have 3 tokens delimited by ';'."));
					}
					Reference <XComponentContext> l_localUnoObjectsContext (defaultBootstrap_InitialComponentContext ());
					Reference <XBridgeFactory> l_unoBridgesFactory (l_localUnoObjectsContext->getServiceManager ()->createInstanceWithContext (UnoExtendedStringHandler::getOustring (string ("com.sun.star.bridge.BridgeFactory")), l_localUnoObjectsContext), UNO_QUERY);
					Reference <InitialUnoObjectsProvider> l_initialUnoObjectsProvider (new InitialUnoObjectsProvider (l_localUnoObjectsContext));
					Reference <UnoConnectionEventsListener> l_unoConnectionEventsListener (new UnoConnectionEventsListener ());
					bool l_ending = false;
					thread l_unoConnectingThread = thread ( [&l_unoServerUrlTokens, &l_localUnoObjectsContext, &l_unoBridgesFactory, &l_initialUnoObjectsProvider, &l_unoConnectionEventsListener, &l_ending] () -> void {
						bool l_connectionIsEstablished = false;
						while (true) {
							l_connectionIsEstablished = false;
							if (l_ending) {
								break;
							}
							{
								unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
								try {
									Reference <XConnection> l_unoConnection;
									list <string>::const_iterator l_unoServerUrlTokensIterator (l_unoServerUrlTokens.begin ());
									try {
										Reference <XConnector> l_unoConnectionConnector (l_localUnoObjectsContext->getServiceManager ()->createInstanceWithContext ("com.sun.star.connection.Connector", l_localUnoObjectsContext), UNO_QUERY);
										l_unoConnection = l_unoConnectionConnector->connect (UnoExtendedStringHandler::getOustring (* (l_unoServerUrlTokensIterator ++)));
									}
									catch (NoConnectException const & l_exception) {
										throw runtime_error (string ("The UNO connection could not be established.") + string (": ") + UnoExtendedStringHandler::getString (l_exception.Message));
									}
									try {
										s_unoConnectionInformation.setUnoBridge (l_unoBridgesFactory->createBridge (UnoExtendedStringHandler::getOustring (string ("")), UnoExtendedStringHandler::getOustring (* (l_unoServerUrlTokensIterator ++)), l_unoConnection, Reference <XInstanceProvider> (l_initialUnoObjectsProvider, UNO_QUERY)));
									}
									catch (BridgeExistsException const & l_exception) {
										// This can't happen
									}
									s_unoConnectionInformation.setRemoteUnoObjectsContext ( Reference <XComponentContext> (s_unoConnectionInformation.getUnoBridge ()->getInstance (UnoExtendedStringHandler::getOustring (* (l_unoServerUrlTokensIterator ++))), UNO_QUERY));
									if (! (s_unoConnectionInformation.getRemoteUnoObjectsContext ().is ())) {
										s_unoConnectionInformation.clear ();
										throw runtime_error (string ("The remote instance is not provided."));
									}
									Reference <XComponent> (s_unoConnectionInformation.getUnoBridge (), UNO_QUERY)->addEventListener (Reference <XEventListener> (l_unoConnectionEventsListener, UNO_QUERY));
									l_connectionIsEstablished = true;
									cout << string ("### A UNO connection has been established.") << endl << flush;
								}
								catch (exception const & l_exception) {
									cout << string ("### An error has occurred: \"") << l_exception.what () << "\"." << endl << flush;
								}
							}
							// This cannot be inside any 's_unoConnectionMaintainingThreadsCondition' lock, because it could cause a dead lock (because there is a reverse locks nest bellow), and this does not have to be inside the above lock, because although the connection might have been severed now, that is inevitable anyway, because the connection can be severed even in the above lock.
							if (l_connectionIsEstablished) {
								unique_lock <recursive_mutex> l_lock (s_unoConnectionUsingThreadsMutex);
								s_unoConnectionUsingThreadsCondition.notify_all ();
							}
							{
								unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
								// This has to be checked, because the connection severance might have been detected and the waking call might have been already dispatched.
								if (! s_unoConnectionInformation.isEmpty ()) {
									// can safely wait, because the possible connection severance has not been detected.
									s_unoConnectionMaintainingThreadsCondition.wait (l_lock);
									// Coming here means that the connection has been severed.
								}
							}
							this_thread::sleep_for (milliseconds (3000));
						}
					});
					
					// Do whatever you want in other daemon threads. Start
					thread l_unoWorkingThread = thread ( [] () -> void {
						bool l_thereWasNoConnection = false;
						bool l_errorHasOccurred = false;
						while (true) {
							try {
								l_thereWasNoConnection = false;
								l_errorHasOccurred = false;
								{
									unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
									if (! s_unoConnectionInformation.isEmpty ()) {
										cout << string ("### Doing something.") << endl << flush;
										Reference <XDesktop> l_unoDesktop (s_unoConnectionInformation.getRemoteUnoObjectsContext ()->getServiceManager ()->createInstanceWithContext (UnoExtendedStringHandler::getOustring (string ("com.sun.star.frame.Desktop")), s_unoConnectionInformation.getRemoteUnoObjectsContext ()), UNO_QUERY);
										cout << string ("### Doing something End.") << endl << flush;
									}
									else {
										l_thereWasNoConnection = true;
										cout << string ("### Warning: there is no UNO connection.") << endl << flush;
									}
								}
							}
							catch (exception const & l_exception) {
								l_errorHasOccurred = true;
								cout << string ("### An error has occurred: \"") << l_exception.what () << "\".";
							}
							// a new connection may have been established now (because 'Test1Test.s_unoConnectionMaintainingThreadsCondition' is not protected here), but there is no problem in checking.
							if (l_thereWasNoConnection || l_errorHasOccurred) {
								{
									unique_lock <recursive_mutex> l_lock (s_unoConnectionUsingThreadsMutex);
									// has to check that there is really no connection, not that just the connection severance is detected, because this thread has to wait in order for the severance to be detected.
									{
										unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
										if (s_unoConnectionInformation.isEmpty ()) {
											l_thereWasNoConnection = true;
										}
										else {
											try {
												s_unoConnectionInformation.getRemoteUnoObjectsContext ()->getServiceManager ();
											}
											catch (exception const & l_exception) {
												l_thereWasNoConnection = true;
											}
										}
									}
									if (l_thereWasNoConnection) {
										// a connection might have been established now (because 's_unoConnectionMaintainingThreadsCondition' is not protected here), but can safely wait, because anyway, the waking call is not dispatched yet at least (because 's_unoConnectionUsingThreadsCondition' has been protected since before the confirmation of non-connection).
										s_unoConnectionUsingThreadsCondition.wait (l_lock);
									}
								}
							}
							this_thread::sleep_for (milliseconds (3000));
						}
					});
					// Do whatever you want in other daemon threads. End
					cout << string ("### Push 'Enter' to quit.") << endl << flush;
					string l_userInputData ("");
					getline (cin, l_userInputData);
					{
						unique_lock <recursive_mutex> l_lock (s_unoConnectionMaintainingThreadsMutex);
						try {
							l_ending = true;
							if (! s_unoConnectionInformation.isEmpty ()) {
								cout << string ("### Severing the UNO connection.") << endl << flush;
								Reference <XComponent> (s_unoConnectionInformation.getUnoBridge (), UNO_QUERY)->dispose ();
								s_unoConnectionInformation.clear ();
							}
							else {
								cout << string ("### No UNO connection is established.") << endl << flush;
							}
						}
						catch (exception const & l_exception) {
							throw runtime_error (string ("The UNO connection could not be severed") + string (": ") + l_exception.what () + string ("."));
						}
					}
					l_unoConnectingThread.join ();
					l_resultStatus = 0;
				}
				catch (exception const & l_exception) {
					cout << "### An error has occurred: \"" << l_exception.what () << "\"." << endl << flush;
				}
				exit (l_resultStatus);
			}
		}
	}
}

Objector 11B
. . . It's long!

Special-Student-7
You know, the length of the code should really matter almost nothing for you; certainly, it mattered some for me, but you can just copy it if you will. Copying 1 line and copying 200 lines are not much different as for your labor.

Objector 11B
I may love the lazy way, actually.

Special-Student-7
Being intimidated by the code length is just an emotional, irrational reaction.


4: Compiling and Linking the Code


Special-Student-7
What you have to take care when you compile the code is that you include the UNO include files directory in the include files paths.

Objector 11B
Where is the directory?

Special-Student-7
In fact, you have to create the directory; please read this article for Linux or this article for Microsoft Windows for details.

Objector 11B
. . . So, I have to generate the header files.

Special-Student-7
What you have to take care when you link the code is that you specify the UNO libraries; please read the same article to know what are the UNO libraries.


5: Executing the Code


Special-Student-7
There are some things you have to do when you execute any C++ external UNO client.

1st, you have to set the UNO dynamic libraries directory path in the appropriate operating system environment variable ('LD_LIBRARY_PATH' for Linux and 'PATH' for Microsoft Windows).

Objector 11B
Ah, I understand that "the UNO dynamic libraries directory path" is the path of the linked UNO libraries.

Special-Student-7
Yes.

And, 2nd, you have to pass an operating system environment variable, namely 'URE_BOOTSTRAP' with the value of '%the office product directory path%/program/fundamentalrc' for Linux or '%the office product directory path%\program\fundamental.ini' for Microsoft Windows.

"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" is the address of the UNO server, to be passed as the argument for the program (of course, change the host and the port for your environment).

Stage Direction
Special-Student-7 executes the program on a terminal. "### The UNO connection could not be established." keeps coming every 3 seconds.

Objector 11B
. . . They are errors. It is not working, right?

Special-Student-7
It is working as expected; it is just that no UNO server is up yet.

Let met start up a UNO server.

Stage Direction
Special-Student-7 starts up a LibreOffice instance. "### A UNO connection has been established." appears on the console, and then "### Doing something Start." and "### Doing something End." start coming every 3 seconds.

Objector 11B
Hmm, a connection has been established. What is it doing?

Special-Student-7
Nothing particularly useful in this sample.

Now, let me kill the UNO server.

Stage Direction
Special-Student-7 kill the LibreOffice instance. "### The UNO connection has been severed." and "### Warning: there is no UNO connection." appear, and then "### The UNO connection could not be established." starts coming again.

Special-Student-7
Let me restart a UNO server.

Stage Direction
Special-Student-7 starts up a LibreOffice instance. "### A UNO connection has been established." appears on the console, and then "### Doing something Start." and "### Doing something End." start coming every 3 seconds.

Objector 11B
So, the "connection-aware" program does not try "Doing something" in vain.

Special-Student-7
Trying "Doing something" in vain amounts to users' doing some futile labor in the real world. The "connection-aware" program spares users such futile labor as much as possible.

Objector 11B
How can the program be stopped?

Special-Student-7
Pushing the 'Enter' key on the console will stop the program.

Stage Direction
Special-Student-7 push the 'Enter' key, and the program stops.


6: Understanding the Code


Special-Student-7
Let us understand the code; I said you could copy the code but I did not say you did not need to understand the code.

Objector 11B
Huh? Understand that lengthy code? It's a trick! The code length matters after all!

Special-Student-7
The issue is what you are comparing it with: compared with opting for the lazy way, just copying without understanding would be no worse, because you would not understand anyway. I am saying that copying and understanding is the best.

Objector 11B
I was tricked!

Special-Student-7
There are 3 standing threads: the main thread, the connection-establishing thread ("l_unoConnectingThread"), and the working thread ("l_unoWorkingThread"), and 1 sporadically evoked thread: the connection-severance-detecting thread.

1st of all, the program has to do bootstrapping, which means that the program becomes a UNO program.

Note that the program has to become a UNO program in order to connect to a UNO server, because the program connects to the UNO server by means of UNO.

Specifically, "defaultBootstrap_InitialComponentContext ()" does the bootstrapping and achieves a local UNO objects context.

"local" means that it is of the program, not of the UNO server.

The local UNO environment has its own UNO services manager, which can instantiate some UNO services in the local UNO environment.

Of course, the local UNO environment does not have any UNO component of like spread sheets documents, but has some UNO components that are necessary in order to connect the local UNO environment to any UNO server, and the program instantiates those UNO components via the local UNO services manager.

In fact, the program instantiates the "com.sun.star.connection.Connector" UNO service.

Then, the program calls the "connect" method of the "com.sun.star.connection.Connector" instance and achieve a "com.sun.star.connection.XConnection" instance.

Objector 11A
So, the program has already connected to the UNO server. I don't understand why it has to do those onerous things afterward.

Special-Student-7
The method name is "connect", but the program has to establish a bridge to the UNO server, in order to really be connected.

Meanwhile, the program prepares an initial UNO objects provider.

Objector 11B
Huh? What is that?

Special-Student-7
The UNO server can achieve some UNO objects from the local program when the bridge is established via the initial UNO objects provider.

Objector 11B
What UNO objects, exactly?

Special-Student-7
That is what the initial UNO objects provider determines, but usually, it is the local UNO objects context, because it is usually via the local UNO objects context that the local UNO environment is handled.

Objector 11B
I don't want my program handled in any way.

Special-Student-7
Then, you do not need to prepare any initial UNO objects provider. In fact, if the UNO server is a LibreOffice or Apache OpenOffice instance, the initial UNO objects provider will not be used anyway.

Then, the program instantiates the "com.sun.star.bridge.BridgeFactory" UNO service.

Then, the program creates a UNO bridge by calling the "createBridge" method of the "com.sun.star.bridge.BridgeFactory" instance.

Then, the program achieves a remote UNO objects context by the "getInstance" method of the "com.sun.star.bridge.XBridge" instance.

That remote UNO objects context is the access point to the UNO server, so you will want to keep it.

For your information, the remote UNO objects context can be gotten from the UNO server because the UNO server offers it via its own initial UNO objects provider.

Objector 11B
I see.

Special-Student-7
The program does one more thing: registering an events listener into the bridge.

The evens listener has to have implemented the "com.sun.star.lang.XEventListener" UNO interface, which has the "disposing" method, which is called when the connection is severed.

The "addEventListener" method registers the events listener into the UNO bridge.

Objector 11B
Ah, I had to do the onerous work in order to grab the bridge, which is required in order to register the listener.

Special-Student-7
Some words on the multiple threads structure of the program.

The "l_unoConnectingThread" thread is active while there is no connection and tries to establish a connection; when the connection is established, the thread begins to wait indefinitely.

The events listener (which runs in an evoked thread) notifies the "l_unoConnectingThread" thread when the connection is severed, and the "l_unoConnectingThread" thread becomes active again, trying to establish a new connection.

The "l_unoWorkingThread" thread is active while there is a connection and does something; when the connection is severed, the thread begins to wait indefinitely.

Imagine that the working thread is dealing with user requests of a Web application; if a user is unlucky, the user might experience an error, but otherwise, the user would be warned before the user did some onerous labor futilely that the Web application was temporarily inoperable (the "### Warning: there is no UNO connection." message represents that situation).

Objector 11B
I understand that, but I don't understand those lengthy comments in the code.

Special-Student-7
Coordination of multiple threads is usually rather delicate, and it is so in this case.

There are some points to be attended.

1st, any dead lock has to be avoided.

In the "l_unoConnectingThread" thread, there is a seemingly tortuous procedure of "s_unoConnectionMaintainingThreadsMutex"'s being locked and then released, and then "s_unoConnectionUsingThreadsMutex"'s being locked and then released, and then "s_unoConnectionMaintainingThreadsMutex"'s being locked and then released, but that is because "s_unoConnectionUsingThreadsMutex" cannot be locked while "s_unoConnectionMaintainingThreadsMutex" is being locked, because that could cause a dead lock.

Objector 11B
Why?

Special-Student-7
The "l_unoWorkingThread" thread has to do lockings in the reverse order, and 2 threads' doing lockings in the reverse orders could cause a dead lock, because the thread 1 could lock an object A; the thread 2 could lock an object B; the thread 1 wanted to lock the object B but could not, because the object B was already locked by the thread 2 while the thread 2 wanted to lock the object A but could not, because the object A was already locked by the thread 1, a dead lock.

Objector 11B
Why does "l_unoWorkingThread" have to do lockings in the reverse order?

Special-Student-7
That is because of the 2nd point to be attended.

Any thread can wait only when it is guaranteed to be properly awaken.

That is because otherwise, the thread could be keep waiting indefinitely.

Objector 11B
How does that require the locking order?

Special-Student-7
In order for the thread to wait on "s_unoConnectionUsingThreadsMutex", "s_unoConnectionUsingThreadsMutex" has to have been locked, but also "s_unoConnectionMaintainingThreadsMutex" has to be locked in order for "s_unoConnectionInformation" to be checked (because "s_unoConnectionMaintainingThreadsMutex" is protecting "s_unoConnectionInformation").

Objector 11B
Why should "s_unoConnectionMaintainingThreadsMutex" not be lock earlier than "s_unoConnectionUsingThreadsMutex" is?

Special-Student-7
Because that would have "s_unoConnectionMaintainingThreadsMutex" keep locked while the thread is waiting.

Objector 11B
So?

Special-Student-7
If so, "l_unoConnectingThread" would keep locked out, without being able to establish a new connection; so, the program would be stuck for ever.

Objector 11B
Of course . . .. Then, why doesn't the thread check "s_unoConnectionInformation" and release "s_unoConnectionMaintainingThreadsMutex", and then lock "s_unoConnectionUsingThreadsMutex"?

Special-Student-7
That is because "s_unoConnectionInformation" might be changed after "s_unoConnectionMaintainingThreadsMutex" is released and before "s_unoConnectionUsingThreadsMutex" is locked, then, the checking would be unreliable.

Objector 11B
But "s_unoConnectionMaintainingThreadsMutex" has been released before the thread starts waiting anyway in your code; according to your theory, a connection might have been already established before the thread starts waiting, .

Special-Student-7
That is really OK, because as "s_unoConnectionUsingThreadsMutex" is locked, the awaking call cannot have been dispatched yet anyway, which is the point. I mean, a connection might have been already established certainly, but the thread is guaranteed to receive the awaking call nevertheless.

Objector 11B
Well, is that so? . . .

Special-Student-7
As for the "l_unoConnectingThread" thread, when it starts waiting, the connection might have been already severed, but the severance is guaranteed to have not been detected yet, so, the awaking call is guaranteed to have not been dispatched yet.

That is because "s_unoConnectionMaintainingThreadsMutex" is locked by the "l_unoConnectingThread" thread and the connection-severance-detecting thread has to lock "s_unoConnectionMaintainingThreadsMutex" before it detects the severance.

Objector 11B
So, you are making sure that the thread does not start waiting after the awaking call has been already dispatched . . .

Special-Student-7
Yes. That is the issue, because otherwise, what the waiting thread is expecting would not come.

And the 3rd point to be attended is that any thread has to wait when another thread must run.

Objector 11B
Huh? That's odd; the another thread could run anyway, because that is what multi-threading is.

Special-Student-7
Multi-threading is not really that, actually. Depending on the run-time environment, only one thread may be able to run at the same time; so, if the any thread keeps running, the another thread may keep stopping.

You may think that "l_unoWorkingThread" does not have to wait, but it has to wait in order for the connection-severance-detecting thread to be guaranteed to run.

Objector 11B
But the connection-severance-detecting thread would run while "l_unoWorkingThread" is "sleep"ing.

Special-Student-7
Please suppose that "sleep" is not there: "sleep" is there just because incessant message outputs are a nuisance.

Objector 11B
What if the program was a Web server?

Special-Student-7
If the program was a Web server, it would not be a loop, and the connection-severance-detecting thread would be evoked between user requests.


7: A Workable Sample


Special-Student-7
Here is a workable sample, which automatically makes a save file of whatever file stored locally by the LibreOffice or Apache OpenOffice instance (note that the LibreOffice or Apache OpenOffice instance is supposed to be local in this case).

The main project is 'connectionAwareExternalUnoClients' and how to build the project is described in a previous article.

The program can be executed like this.

@bash or CMD Source Code
gradle i_executeCplusplusExecutableFileTask  -Pi_commandLineArguments="\"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext\""

Note that its code is not written as flatly as the above code, but what is being done is the same, stashed in my utility classes like '::theBiasPlanet::unoUtilities::processesHandling::UnoProcessEnvironment.UnoProcessEnvironment', '::theBiasPlanet::unoUtilities::connectionsHandling::UnoConnectionConnector::UnoConnectionConnector', and '::theBiasPlanet::unoUtilities::connection::UnoConnection::UnoConnection'.

Objector 11B
"flatly"?

Special-Student-7
The above code has been written only for the explanation's sake; I usually do not write that way; I usually more appropriately organize things in classes.

For example, that chunk of connection-establishing procedure is a boilerplate, which I am not grad to flatly write in each UNO external client, so I confine it in a utility class; UNO has 'com.sun.star.connection.XConnection' and 'com.sun.star.bridge.XBridge' objects, but that distinction is not really helpful for me, so I have a single class that wholly represents a UNO connection; especially, as UNO is somewhat cumbersome in that we have to take out various UNO proxies of the same UNO object, I have a utility class ('::theBiasPlanet::unoUtilities::pointers::UnoObjectPointer') that corresponds to the UNO component, not to each UNO interface, and have the utility class handle such various UNO proxies.


References


<The previous article in this series | The table of contents of this series | The next article in this series>