2021-11-14

66: 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 14A, and Objector 14B 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 14A
Am I being expected to feel a kind of scruple in connecting my program to a UNO server?

Special-Student-7
Sir, "scrupulous" here is more about meticulousness than about morals.

Objector 14A
Why do I have to be meticulous?

Special-Student-7
You do not particularly "have to", but that lazy way has some inconveniences.

Objector 14A
What do you mean by "that lazy way"? What lazy way?

Special-Student-7
The most pervasively promoted way is the lazy way, which insists that the UNO server should be in the local computer, gratuitously starts a LibreOffice or Apache OpenOffice instance in the local computer, and connects to the local LibreOffice or Apache OpenOffice instance, and does not detect if the connection is severed.

Objector 14A
Is that a problem?

Special-Student-7
May not be one for you, but we are supposing that our program may be a long-standing one like a Web application or a GUI program, and it may be in an application server or in a client computer, while the UNO server may be in a remote data server, and the connection may be severed in a reason and the long-standing program will usually want to know if the connection is severed.

Objector 14A
The long-standing program would know the severance anyway, because any procedure that used the connection would fail.

Special-Student-7
It would, but users would want to be informed before he or she inputted a lot of data on the screen, pushed a button, and found his or her effort futile, if the severance could be detected sooner.

Objector 14A
Supposing that the program has detected the severance, so what? 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 14A
Such sabotage is another story . . .

Special-Student-7
Anyway, detecting the connection severances fast and doing it in an orderly way is a good thing.

Objector 14A
"an orderly way"?

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 14B
You keep saying "UNO server", but it is really a LibreOffice or Apache OpenOffice instance, right?

Special-Student-7
Madam, usually so, but not always.

Objector 14B
When is it not so?

Special-Student-7
When it is a UNO server you or someone have created.

Objector 14B
Huh? Have anyone created one?

Special-Student-7
I have; it is rather easy to create, in fact.

Objector 14B
But I have no motivation.

Special-Student-7
I understand; I do not particularly intend to implant a motivation here; I just do not feel it necessary to exclude the possibility.

You are free to assume that "UNO server" is a LibreOffice or Apache OpenOffice instance, for your purpose, but please note that the LibreOffice or Apache OpenOffice instance has to be made a UNO server, in order 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.


2: Some Notes


Special-Student-7
Note that .NET Core cannot be used: proper .NET framework is required, as far as I know, at least for the LibreOffice 7.2 version or older.

Objector 14B
Well, can't I use C# for Linux LibreOffice?

Special-Student-7
You cannot; the SDK does not have .NET libraries, does it?

Objector 14B
Don't ask me.

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


3: The Code


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

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.cs

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace connectingFromExternalProgramTest1 {
			using System;
			using System.Runtime.InteropServices;
			using System.Threading;
			using uno.util;
			using unoidl.com.sun.star.bridge;
			using unoidl.com.sun.star.connection;
			using unoidl.com.sun.star.frame;
			using unoidl.com.sun.star.lang;
			using unoidl.com.sun.star.uno;
			
			public class Test1Test {
				[StructLayout (LayoutKind.Sequential)]
				internal struct WSAData {
					public Int16 wVersion;
					public Int16 wHighVersion;
					[MarshalAs (UnmanagedType.ByValTStr, SizeConst=257)]
					public String szDescription;
					[MarshalAs (UnmanagedType.ByValTStr, SizeConst=129)]
					public String szSystemStatus;
					public Int16 iMaxSockets;
					public Int16 iMaxUdpDg;
					public Int32 lpVendorInfo;
				}
				[DllImport ("ws2_32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
				internal static extern Int32 WSAStartup (
					[In] Int16 wVersionRequested,
					[Out] out WSAData lpWSAData
				);
				[DllImport ("ws2_32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
				internal static extern Int32 WSACleanup ();
				private const Int32 c_windowsSocketStartupSuccessStatus = 0;
				private const Int16 c_windowsSocketVersion = 0x0202;
				
				public static void windowsSocketStartup () {
					WSAData l_windowsSocketData;
					Int32 l_windowsSocketStartupStatus = WSAStartup (c_windowsSocketVersion, out l_windowsSocketData);
					if (l_windowsSocketStartupStatus != c_windowsSocketStartupSuccessStatus) {
						throw new System.Exception (String.Format ("WSAStartup failed: return code = {0:D}", l_windowsSocketStartupStatus));
					}
				}
				
				public static void windowsSocketCleanup () {
					WSACleanup ();
				}
				
				public class InitialUnoObjectsProvider: WeakBase, XInstanceProvider {
					private XComponentContext i_localUnoObjectsContext;
					
					public InitialUnoObjectsProvider (XComponentContext a_localUnoObjectsContext) {
						i_localUnoObjectsContext = a_localUnoObjectsContext;
					}
					
					~InitialUnoObjectsProvider () {
					}
					
					// 'XInterface' cannot be used for C#
					virtual public Object getInstance (String a_initialUnoObjectName) {
						if ("theBiasPlanet.UnoObjectsContext" == a_initialUnoObjectName) {
							return i_localUnoObjectsContext;
						}
						else {
							return null;
						}
					}
				}
				
				public class UnoConnectionEventsListener: WeakBase, XEventListener {
					public UnoConnectionEventsListener () {
					}
					
					~UnoConnectionEventsListener () {
					}
					
					public void disposing (EventObject a_event) {
						Console.Out.WriteLine ("### The UNO connection has been severed.");
						Console.Out.Flush ();
						lock (s_unoConnectionMaintainingThreadsCondition) {
							s_unoConnectionInformation.clear ();
							Monitor.PulseAll (s_unoConnectionMaintainingThreadsCondition);
						}
					}
				}
				
				public class UnoConnectionInformation {
					private XBridge i_unoBridge;
					private XComponentContext i_remoteUnoObjectsContext;
					
					public UnoConnectionInformation () {
						i_unoBridge = null;
						i_remoteUnoObjectsContext = null;
					}
					
					~UnoConnectionInformation () {
					}
					
					public Boolean isEmpty () {
						return i_remoteUnoObjectsContext == null;
					}
					
					public XBridge getUnoBridge () {
						return i_unoBridge;
					}
					
					public XComponentContext getRemoteUnoObjectsContext () {
						return i_remoteUnoObjectsContext;
					}
					
					public void setUnoBridge (XBridge a_unoBridge) {
						i_unoBridge = a_unoBridge;
					}
					
					public void setRemoteUnoObjectsContext (XComponentContext a_remoteUnoObjectsContext) {
						i_remoteUnoObjectsContext = a_remoteUnoObjectsContext;
					}
					
					public void clear () {
						i_unoBridge = null;
						i_remoteUnoObjectsContext = null;
					}
				}
				
				static Object s_unoConnectionMaintainingThreadsCondition = new Object ();
				static Object s_unoConnectionUsingThreadsCondition = new Object ();
				static UnoConnectionInformation s_unoConnectionInformation = new UnoConnectionInformation ();
				
				public void main (String [] a_argumentsArray) {
					Int32 l_resultStatus = -1;
					try {
						if (a_argumentsArray.Length != 2) {
							throw new System.Exception ("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 [1];
						String [] l_unoServerUrlTokensArray = l_unoServerUrl.Split (";".ToCharArray ());
						if (l_unoServerUrlTokensArray.Length < 3) {
							throw new System.Exception ("The server URL has to have 3 tokens delimited by ';'.");
						}
						XComponentContext l_localUnoObjectsContext = Bootstrap.defaultBootstrap_InitialComponentContext ();
						windowsSocketStartup ();
						XBridgeFactory l_unoBridgesFactory = (XBridgeFactory) l_localUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.bridge.BridgeFactory", l_localUnoObjectsContext);
						InitialUnoObjectsProvider l_initialUnoObjectsProvider = new InitialUnoObjectsProvider (l_localUnoObjectsContext);
						UnoConnectionEventsListener l_unoConnectionEventsListener = new UnoConnectionEventsListener ();
						Boolean l_ending = false;
						Thread l_unoConnectingThread = new Thread ( () => {
							Boolean l_connectionIsEstablished = false;
							while (true) {
								l_connectionIsEstablished = false;
								if (l_ending) {
									break;
								}
								lock (s_unoConnectionMaintainingThreadsCondition) {
									try {
										XConnection l_unoConnection = null;
										try {
											XConnector l_unoConnectionConnector = (XConnector) l_localUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.connection.Connector", l_localUnoObjectsContext);
											l_unoConnection = l_unoConnectionConnector.connect (l_unoServerUrlTokensArray [0]);
										}
										catch (NoConnectException l_exception) {
											throw new System.Exception (String.Format ("{0:s}: {1:s}", "The UNO connection could not be established.", l_exception.ToString ()));
										}
										try {
											s_unoConnectionInformation.setUnoBridge (l_unoBridgesFactory.createBridge ("", l_unoServerUrlTokensArray [1], l_unoConnection, l_initialUnoObjectsProvider));
										}
										catch (BridgeExistsException l_exception) {
											// This can't happen
										}
										s_unoConnectionInformation.setRemoteUnoObjectsContext ( (XComponentContext) s_unoConnectionInformation.getUnoBridge ().getInstance (l_unoServerUrlTokensArray [2]));
										if (s_unoConnectionInformation.getRemoteUnoObjectsContext () == null) {
											s_unoConnectionInformation.clear ();
											throw new System.Exception ("The remote instance is not provided.");
										}
										( (XComponent) s_unoConnectionInformation.getUnoBridge ()).addEventListener (l_unoConnectionEventsListener);
										l_connectionIsEstablished = true;
										Console.Out.WriteLine ("### A UNO connection has been established.");
										Console.Out.Flush ();
									}
									catch (System.Exception l_exception) {
										Console.Error.WriteLine (String.Format ("### An error has occurred: \"{0:s}\".", l_exception.ToString ()));
										Console.Error.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) {
									lock (s_unoConnectionUsingThreadsCondition) {
										Monitor.PulseAll (s_unoConnectionUsingThreadsCondition);
									}
								}
								lock (s_unoConnectionMaintainingThreadsCondition) {
									// 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.
										Monitor.Wait (s_unoConnectionMaintainingThreadsCondition);
										// Coming here means that the connection has been severed.
									}
								}
								Thread.Sleep (3000);
							}
						});
						l_unoConnectingThread.Start ();
						
						// Do whatever you want in other daemon threads. Start
						Thread l_unoWorkingThread = new Thread ( () => {
							Boolean l_thereWasNoConnection = false;
							Boolean l_errorHasOccurred = false;
							while (true) {
								try {
									l_thereWasNoConnection = false;
									l_errorHasOccurred = false;
									lock (s_unoConnectionMaintainingThreadsCondition) {
										if (! s_unoConnectionInformation.isEmpty ()) {
											Console.Out.WriteLine ("### Doing something.");
											Console.Out.Flush ();
											XDesktop l_unoDesktop = (XDesktop) s_unoConnectionInformation.getRemoteUnoObjectsContext ().getServiceManager ().createInstanceWithContext ("com.sun.star.frame.Desktop", s_unoConnectionInformation.getRemoteUnoObjectsContext ());
											Console.Out.WriteLine ("### Doing something End.");
											Console.Out.Flush ();
										}
										else {
											l_thereWasNoConnection = true;
											Console.Out.WriteLine (String.Format ("{0:s}", "### Warning: there is no UNO connection."));
											Console.Out.Flush ();
										}
									}
								}
								catch (System.Exception l_exception) {
									l_errorHasOccurred = true;
									Console.Error.WriteLine (String.Format ("### An error has occurred: \"{0:s}\".", l_exception.ToString ()));
									Console.Error.Flush ();
								}
								// 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) {
									lock (s_unoConnectionUsingThreadsCondition) {
										// 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.
										lock (s_unoConnectionMaintainingThreadsCondition) {
											if (s_unoConnectionInformation.isEmpty ()) {
												l_thereWasNoConnection = true;
											}
											else {
												try {
													s_unoConnectionInformation.getRemoteUnoObjectsContext ().getServiceManager ();
												}
												catch (System.Exception 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).
											Monitor.Wait (s_unoConnectionUsingThreadsCondition);
										}
									}
								}
								Thread.Sleep (3000);
							}
						});
						l_unoWorkingThread.IsBackground = true;
						l_unoWorkingThread.Start ();
						// Do whatever you want in other daemon threads. End
						Console.Out.WriteLine ("### Push 'Enter' to quit.");
						Console.Out.Flush ();
						Console.ReadLine ();
						lock (s_unoConnectionMaintainingThreadsCondition) {
							try {
								l_ending = true;
								if (! s_unoConnectionInformation.isEmpty ()) {
									Console.Out.WriteLine ("### Severing the UNO connection.");
									Console.Out.Flush ();
									( (XComponent) s_unoConnectionInformation.getUnoBridge ()).dispose ();
									s_unoConnectionInformation.clear ();
								}
								else {
									Console.Out.WriteLine ("### No UNO connection is established.");
									Console.Out.Flush ();
								}
							}
							catch (System.Exception l_exception) {
								throw new System.Exception (String.Format ("{0:s}: {1:s}.", "The UNO connection could not be severed", l_exception.ToString ()));
							}
						}
						l_unoConnectingThread.Join ();
						windowsSocketCleanup ();
						l_resultStatus = 0;
					}
					catch (System.Exception l_exception) {
						Console.Error.WriteLine (String.Format ("### An error has occurred: \"{0:s}\".", l_exception.ToString ()));
						Console.Error.Flush ();
					}
					Environment.Exit (l_resultStatus);
				}
			}
		}
	}
}

Objector 14B
. . . Does it have to be so long? Being "scrupulous" is not so a casual thing to do, I see.

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.

Objector 14B
But it's over 200 lines!

Special-Student-7
Copying 1 line and copying 200 lines are not much different as for your labor.

Being intimidated by the code length is just an emotional, irrational reaction.


4: Compiling the Code


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

Objector 14B
Where are the UNO assemblies?

Special-Student-7
They are in the 'cli' directory of the LibreOffice or Apache OpenOffice SDK directory.

Objector 14B
Where is the LibreOffice SDK directory?

Special-Student-7
I do not know for your environment; you should have specified it when you installed the SDK.


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 have copied the UNO assemblies into the execution file directory. . . . Honestly, I am not happy about having to copy the UNO assemblies around, but .NET does not allow specifying any assemblies path at execution.

Objector 14B
You can register them into the global cache.

Special-Student-7
I know, but I prefer not, as I deem that each program should be allowed to choose the assemblies it uses.

Anyway, 2nd, you have to pass an operating system environment variable, namely 'URE_BOOTSTRAP' with the value of '%the office product directory path%\program\fundamental.ini'.

"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 14B
. . . "UNO connection could not be established" . . .. Why could it not be established?

Special-Student-7
Because no UNO server is up yet.

Let me 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 14B
Oh, a connection has been established, hasn't it?

Special-Student-7
It has.

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 14B
Hmm, that is what "connection-aware" means.

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 14B
Huh? That's a fraud! The code length matters after all!

Special-Student-7
Well, I do not coerce you if you do not want to; 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 14B
. . .

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, "Bootstrap.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 14A
So, the program has already connected to the UNO server. What is it doing 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 14B
"initial UNO . . .", I have never heard of such a thing.

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 14B
"some UNO objects"? What UNO objects, exactly?

Special-Student-7
Whatever the program offers, 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 14B
How would the LibreOffice instance handle my program?

Special-Student-7
In no way actually. So, if the UNO server is a LibreOffice or Apache OpenOffice instance, the initial UNO objects provider is practically meaningless.

So, your program does not really have to prepare any initial UNO objects provider, although our program has done so, because it is meant to be scrupulous.

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.

You know, 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 14B
Ah, so, it is mutual.

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 14B
The connection is ready at last.

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 14B
I understand that far; but what are those slobbery comments in the code?

Special-Student-7
"slobbery"? . . . 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_unoConnectionMaintainingThreadsCondition"'s being locked and then released, and then "s_unoConnectionUsingThreadsCondition"'s being locked and then released, and then "s_unoConnectionMaintainingThreadsCondition"'s being locked and then released, but that is because "s_unoConnectionUsingThreadsCondition" cannot be locked while "s_unoConnectionMaintainingThreadsCondition" is being locked, because that could cause a dead lock.

Objector 14B
Could that?

Special-Student-7
Yes, 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 14B
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 14B
I understand, but how is that related with the locking order?

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

Objector 14B
Why doesn't the thread lock "s_unoConnectionMaintainingThreadsCondition" first and then lock "s_unoConnectionUsingThreadsCondition"?

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

Objector 14B
Is that a problem?

Special-Student-7
Of course. 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 14B
Of course . . .. Then, why doesn't the thread check "s_unoConnectionInformation" and release "s_unoConnectionMaintainingThreadsCondition", and then lock "s_unoConnectionUsingThreadsCondition"?

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

Objector 14B
But "s_unoConnectionMaintainingThreadsCondition" has been released before the thread starts waiting anyway in your code; is that OK? I mean, a connection might have been already established before the thread starts waiting, according to your theory.

Special-Student-7
That is really OK, because as "s_unoConnectionUsingThreadsCondition" 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 14B
Ah, it's delicate, in fact.

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.

Objector 14B
Ah, that is because "s_unoConnectionMaintainingThreadsCondition" is locked by the "l_unoConnectingThread" thread and the connection-severance-detecting thread has to lock "s_unoConnectionMaintainingThreadsCondition" before it detects the severance.

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 14B
Um? Is that so? The another thread could run without the any thread waiting, because, you know, it is a multi-threading.

Special-Student-7
Not necessarily, depending on the run-time environment. Multiple threads may not really run simultaneously: a thread may keep running unless it waits.

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 14B
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 14B
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_executeCsharpExecutableFileTask -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 14B
. . . What do you mean by "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.

Objector 14B
"organize things in classes"?

Special-Student-7
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.

Objector 14B
Ah.


References


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