2022年1月9日日曜日

66: コネクションアウェアなリモートにも置けるC#外部UNOクライアント

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

LibreOffice/OpenOfficeの機能をあなたの自立した(非マクロ)プログラム(Webアプリケーションのような長時間動作するものでもよい)に組み込むために

話題


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

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、自分の外部C#プログラムを、リモートコンピュータにあってもよい任意のUNOサーバー(通常は、LibreOfficeまたはApache OpenOfficeインスタンス)へ、コネクションアウェアに接続する方法を知る。

オリエンテーション


LibreOfficeまたはApache OpenOfficeデーモンを作成する方法の記事およびLibreOfficeまたはApache OpenOffice Microsoft Windowsサービスを作成する方法の記事があります。

LibreOfficeまたはApache OpenOfficeをファイルコンバーターとして最適に利用することについての記事があります。

C#にて任意のUNOディスパッチコマンドを実行して実行の全情報を取得することについての記事があります。

C#にて任意のUNOコンポーネントを作成することについての記事があります。

コネクションアウェアな外部UNOクライアントを他のプログラミング言語で作成することについての記事が公開される予定ですPython)。


本体

ト書き
Special-Student-7、Objector 14A、Objector 14Bがコンピュータの前にいる。


1: 目標の明確化


Special-Student-7
本記事の目標は、自分の自立C#プログラムを任意のUNOサーバーに、scrupulous(良心的な、用意周到な)に接続することです。

"UNOサーバー"は通常LibreOfficeまたはApache OpenOfficeインスタンスであり、"自立"は、プログラムがLibreOfficeまたはApache OpenOfficeインスタンス内マクロでないことを意味します。

Objector 14A
自分のプログラムをUNOサーバーへ接続するのに、私はある種の良心の呵責を感じるように期待されているのか?

Special-Student-7
サー、ここでの"scrupulous"は、モラルの問題というよりも用意周到さの問題です。

Objector 14A
なぜ私は用意周到でなければならないのか?

Special-Student-7
特に"なければならない"わけではありませんが、あの怠惰な方法は不便さを含んでいます。

Objector 14A
"あの怠惰な方法"とは何のことだ?どの怠惰な方法だ?

Special-Student-7
最も広く宣伝されている方法がその怠惰な方法であり、それは、UNOサーバーはローカルコンピュータにいなければならないと言い、頼みもしないのにLibreOfficeまたはApache OpenOfficeインスタンスをローカルコンピュータに開始し、そのローカルLibreOfficeまたはApache OpenOfficeインスタンスに接続し、コネクションが切断されても検知しません。

Objector 14A
それが問題か?

Special-Student-7
あなたにはそうでないかもしれませんが、私たちが想定しているのは、私たちのプログラムは、WebアプリケーションかGUIプログラムのような長時間動作し続けるものかもしれず、アプリケーションサーバーかクライアントコンピュータにいるかもしれず、その一方で、UNOサーバーはリモートデーターサーバーにいるかもしれず、コネクションはある理由によって切断されるかもしれず、長時間動作し続けるプログラムは、通常、もしもコネクションが切断されたらそれを知りたいだろうということです。

Objector 14A
長時間動作するプログラムは切断をいずれにせよ知るだろう、だって、その切断を使う処理は失敗するんだから。

Special-Student-7
それは知るでしょう、しかし、ユーザーは、自分が沢山のデータを画面上で入力し、ボタンを押し、自分の努力が無駄であったと悟る前に、通知して欲しいと思うでしょう、もしも、切断がもっと早く検知できるのであれば。

Objector 14A
プログラムは切断を検知したとして、だから何だ?プログラムはコネクションを自動的に再確立できるのか?

Special-Student-7
コネクションを自動的に再確立するよう試みることはできます、しかし、問題の原因が勿論まず取り除かれなければなりません、試みが作成するためには。

例えば、UNOサーバーの死去が原因であれば、新たなUNOサーバーを自動的に開始させることができるかもしれませんが、もしも、誰かがあるケーブルを引き抜いたのであれば、そのケーブルは手で元に戻されなければならないでしょう。

Objector 14A
そういう妨害行為は別の話だ . . .

Special-Student-7
いずれにせよ、コネクションの切断を速やかに検知することおよびそれを整然とした方法で行なうことはよいことです。

Objector 14A
"整然とした方法で"?

Special-Student-7
各モジュールに切断の検知および対処を、失敗することによって行なわせるよりも、単一のイベントリスナーに集中的にそうした切断の検知および対処を行なわせるほうが、私には整然とした方法です。

Objector 14B
あなたは"UNOサーバー"と言い続けてるけど、それは実際にはLibreOfficeまたはApache OpenOfficeインスタンスなんでしょう?

Special-Student-7
マダム、大抵はそうですが、常にではありません。

Objector 14B
いつ、そうではないの?

Special-Student-7
それが、あなたもしくは他のどなたかが作成されたUNOサーバーであるときです。

Objector 14B
はあ?誰か作った人いるの?

Special-Student-7
私は作成しました; 作成するのはかなり容易です、実のところ。

Objector 14B
でも私には動機がない。

Special-Student-7
分かります; ここで動機を植え付けようという意図は私には特にありません; ただ、その可能性を除外する必要があるとは感じないというだけです。

あなたは自由に、あなたの目的上、"UNO server"はLibreOfficeまたはApache OpenOfficeインスタンスだと見なすことができます、しかし、そのLibreOfficeまたはApache OpenOfficeインスタンスはUNOサーバーにされないといけないことにご注意ください、UNOサーバーであるためには。

UNOサーバーはリモートコンピュータ内にいてもいいですが、その場合には、そのUNOサーバーはTCP/IPソケットプロトコルを使わなければなりません。


2: 注意事項


Special-Student-7
.NET Coreは使用できないことにご注意ください: 本物の.NET frameworkが必要とされます、私が知る限りでは、少なくともLibreOffice 7.2またはそれより古いバージョンでは。

Objector 14B
えーと、Linux LibreOfficeにC#を使えないの?

Special-Student-7
お使いになれません; SDKに.NETライブラリが含まれていないでしょう?

Objector 14B
私に聞かないで。

Special-Student-7
適切な開発環境が準備済みであると想定されています(Linux用またはMicrosoft Windows用の記事があります)。


3: コード


Special-Student-7
前置きはそれぐらいにして、以下がコードです。

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.cs

@C# ソースコード
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
. . . そんなに長くないといけないの?"scrupulous"であるというのはそんなに気楽なことじゃないようねえ。

Special-Student-7
お分かりでしょうが、コードの長さはあなたにとって実際にはほとんど何の問題でもないはずです; たしかに、私にはいくらか問題でしたが、あなたはそれをただコピーすればよいだけです、もしもおよろしければ。

Objector 14B
でも、200行以上よ!

Special-Student-7
1行をコピーするのも200行をコピーするのも、あなたの労力はたいして違いません。

コードの長さに怖じ気づくのは、単に、情緒的な、非理性的な反応です。


4: コードをコンパイルする


Special-Student-7
コードをコンパイルされる際におやりいただかないといけないのは、アセンブリパスにUNOアセンブリ群を含めることです。

Objector 14B
そのUNOアセンブリ群はどこにあるの?

Special-Student-7
それらは、LibreOfficeまたはApache OpenOffice SDKの'cli'ディレクトリ内にあります。

Objector 14B
LibreOffice SDKディレクトリはどこにあるの?

Special-Student-7
あなたの環境のことは存じません; そのSDKをインストールされた際にご自分で指定されたはずです。


5: コードを実行する


Special-Student-7
C#外部UNOクライアントを実行する際には行わなければならないいくつかのことがあります。

第1に、UNOアセンブリ群を実行ファイルディレクトリの中へコピーしなければなりません。 . . . 正直なところ、UNOアセンブリ群をコピーし回さないといけないというのは私には嬉しくないことですが、.NETは、実行時にアセンブリパスを指定させてくれません。

Objector 14B
グローバルキャッシュに登録できるけど。

Special-Student-7
存じていますが、私はそうしないことを好みます、各プログラムは自身が使用するアセンブリ群を選択できるべきだと考えますから。

それはとにかく、第2に、あるオペレーティングシステム環境変数を渡さなければなりません、具体的には、'URE_BOOTSTRAP'で、値は、'%オフィスプロダクトディレクトリパス%\program\fundamental.ini'です。

"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext"が、プログラムへの引数として渡すべきUNOサーバーのアドレスです(勿論、あなたの環境に合わせて、ホストおよびポートをご変更ください)。

ト書き
Special-Student-7は、あるターミナル上でプログラムを実行する。 "### The UNO connection could not be established."が3秒おきに現わ続ける。

Objector 14B
. . . "UNO connection could not be established" . . .. なんで確立できなかったわけ?

Special-Student-7
UNOサーバーがまだ立ち上がっていないからです。

UNOサーバーを開始させてください。

ト書き
Special-Student-7はLibreOfficeインスタンスを開始する。 「### A UNO connection has been established.」がコンソール上に現われる、次に、「### Doing something Start.」および「### Doing something End.」が3秒おきに現われ始める。

Objector 14B
ああ、コネクションが確立されたわね?

Special-Student-7
されました。

それでは、UNOサーバーを殺させてください。

ト書き
Special-Student-7は、LibreOfficeインスタンスを殺す。 「### The UNO connection has been severed.」および「### Warning: there is no UNO connection.」が現われ、次いで、「### The UNO connection could not be established.」が再び現れ始める。

Special-Student-7
LibreOfficeインスタンスを再開始させてください。

ト書き
Special-Student-7はLibreOfficeインスタンスを開始する。 「### A UNO connection has been established.」がコンソール上に現われ、次いで、「### Doing something Start.」および「### Doing something End.」が3秒おきに現われ始める。

Objector 14B
ふーん、それが、"コネクションアウェア"が意味することなのね。

Special-Student-7
コンソール上で'Enter'キーを押せばプログラムが停止します。

ト書き
Special-Student-7は'Enter'キーを押し、プログラムが停止する。


6: コードを理解する


Special-Student-7
コードを理解しましょう。あなたはコードをコピーできると申しましたが、あなたはコードを理解する必要はないとは申しませんでした。

Objector 14B
はあ?それはサギよ!コードの長さは問題じゃないの、結局のところ!

Special-Student-7
えーと、お望みでなければ無理強いはしません; 問題は、それを何と比較しておられるかということです: 怠惰な方法を選択するのと比較すれば、理解せずにただコピーするのは少しも悪くありません、あなたはどのみち理解されないのですから。私は、コピーして理解するのがベストだと申し上げているのです。

Objector 14B
. . .

Special-Student-7
3つの常時スレッド: メインスレッド、コネクション確立スレッド、("l_unoConnectingThread")、ワーキングスレッド("l_unoWorkingThread")、および1つの散発的に起動されるスレッド: コネクション切断検出スレッドがあります。

まず最初に、プログラムはブートストラッピング(プログラムがUNOプログラムになることを意味します)を行わなければなりません。

プログラムは、UNOサーバーに接続するには、UNOプログラムにならなければならないということです、なぜなら、プログラムはUNOサーバーにUNOを手段として接続するからです。

具体的には、"Bootstrap.defaultBootstrap_InitialComponentContext ()"がブートストラッピングを行ない、ローカルUNOオブジェクト群コンテキストを獲得します。

"ローカル"は、それが、そのプログラムのものであり、UNOサーバーのものではないことを意味します。

ローカルUNO環境はそれ自身のUNOサービス群マネージャーを持っており、そのUNOサービス群マネージャーはいくつかのUNOサービスをローカルUNO環境内にインスタンス化できます。

ローカルUNO環境には、勿論、スプレッドシートドキュメントのようなUNOコンポーネントはないが、ローカルUNO環境をUNOサーバーに接続するために必要ないくつかのUNOコンポーネントがあり、プログラムは、それらのUNOコンポーネントをローカルUNOサービス群マネージャーを介してインスタンス化します。 

実際、プログラムは"com.sun.star.connection.Connector"UNOサービスをインスタンス化します。

次に、プログラムは"com.sun.star.connection.Connector"インスタンスの"connect"メソッドをコールして、"com.sun.star.connection.XConnection"インスタンスを獲得します。

Objector 14A
それじゃあ、プログラムは既にUNOサーバーに接続したわけだ。その後に何をやっている?

Special-Student-7
そのメソッド名は「connect」ですが、プログラムはUNOサーバーへのブリッジを確立しなければなりません、本当に接続するためには。

その一方で、プログラムはイニシャルUNOオブジェクト群プロバイダを用意します。

Objector 14B
"イニシャルUNO . . ."、そんなもの聞いたことないけど。

Special-Student-7
UNOサーバーはローカルプログラムからいくつかのUNOオブジェクトを獲得できます、ブリッジが確立されたときにイニシャルUNOオブジェクト群プロバイダを介して。

Objector 14B
"いくつかのUNOオブジェクト"?どのUNOオブジェクト、具体的には?

Special-Student-7
プログラムがオファーしているもの何でもです、しかし、通常は、それは、ローカルUNOオブジェクト群コンテキストです、なぜなら、ローカルUNO環境が操作されるのは、通常、ローカルUNOオブジェクト群コンテキストを介してなので。

Objector 14B
LibreOfficeインスタンスがどう私のプログラムを操作するというの?

Special-Student-7
実のところ、どのようにもしません。したがって、UNOサーバーがLibreOfficeまたはApache OpenOfficeインスタンスであれば、イニシャルUNOオブジェクト群プロバイダは実際上無意味です。

したがって、あなたのプログラムは実際にイニシャルUNOオブジェクト群プロバイダを用意する必要はありません、私たちのプログラムはそうしましたが、scrupulousであるように意図されているが故に

次に、プログラムは"com.sun.star.bridge.BridgeFactory"UNOサービスをインスタンス化します。

次に、プログラムはUNOブリッジを生成します、"com.sun.star.bridge.BridgeFactory"インスタンスの"createBridge"メソッドをコールすることによって。

次に、プログラムは、リモートUNOオブジェクト群コンテキストを獲得します、"com.sun.star.bridge.XBridge"インスタンスの"getInstance"メソッドをコールすることによって。

お分かりでしょうが、そのリモートUNOオブジェクト群コンテキストがUNOサーバーへのアクセスポイントになるので、それをキープされたいでしょう。

情報として申し上げると、そのリモートUNOオブジェクト群コンテキストがUNOサーバーから取得できるのは、UNOサーバーがそれを自身のイニシャルUNOオブジェクト群プロバイダを介してオファーしているからです。

Objector 14B
ああ、それじゃあ、お互いにオファーしてるのね。

Special-Student-7
プログラムはもう1つのことを行ないます: イベントリスナーをブリッジに登録します。

そのイベントリスナーは"com.sun.star.lang.XEventListener"UNOインターフェイス("disposing"メソッドを持っており、そのメソッドはコネクションが切断されたときに呼ばれる)を実装しておかなければなりません。

"addEventListener"メソッドは当該イベントリスナーをUNOブリッジへ登録します。

Objector 14B
コネクションがようやく準備できた。

Special-Student-7
当プログラムのマルチスレッド構造についていくらか言っておきましょう。

"l_unoConnectingThread"スレッドは、コネクションがない間はアクティブであって、コネクションを確立しようとします。コネクションが確立されると、そのスレッドは無期限に待ち始めます。

イベントリスナー(呼び起こされるスレッドで走る)は、コネクションが切断されると"l_unoConnectingThread"スレッドに通知し、"l_unoConnectingThread"スレッドは再びアクティブになり、新たなコネクションを確立しようと試みます。

"l_unoWorkingThread"スレッドは、コネクションがある間はアクティブであり、何かを行ないます。コネクションが切断されると、スレッドは無期限に待ち始めます。

ワーキングスレッドはWebアプリケーションのユーザーリクエスト群に対処しているものと想像してください。もしも、ユーザーが不運だったら、ユーザーはエラーを経験するかもしれませんが、そうでなければ、ユーザーは、労苦を無益に行なう前に、Webアプリケーションは一時的に処理不能状態にあると警告されるでしょう(前出の"### Warning: there is no UNO connection."メッセージはその状況を表わしています)。

Objector 14B
そこまでは分かるけど、コード中のあれらのめそめそしたコメントは何なの?

Special-Student-7
"めそめそした"? . . . マルチスレッドのコーディネートは通常かなりデリケートなものですが、本ケースにおいてもそうです。

面倒を見なければならないポイントがいくつかあります。

第1に、デッドロックは避けなければなりません。

"l_unoConnectingThread"スレッド内に、"s_unoConnectionMaintainingThreadsCondition"がロックされて次にリリースされ、次に"s_unoConnectionUsingThreadsCondition"がロックされて次にリリースされ、そして次に"s_unoConnectionMaintainingThreadsCondition"がロックされて次にリリースされるという、曲折したように思われる処置がありますが、その理由は、"s_unoConnectionUsingThreadsCondition"は"s_unoConnectionMaintainingThreadsCondition"がロックされている最中にロックできないことです、それはデッドロックを起こす可能性がありますから。

Objector 14B
そお?

Special-Student-7
はい、"l_unoWorkingThread"スレッドは、逆の順序でロックを行わなければならず、2つのスレッドが互いに逆順にロックを行なうと、デッドロックを起こす可能性があります、なぜなら、スレッド1がオブジェクトAをロックし、スレッド2がオブジェクトBをロックし、スレッド1はオブジェクトBをロックしたいが、できない、オブジェクトBは既にスレッドによってロックされているから、その一方でスレッド2はオブジェクトAをロックしたいが、できない、オブジェクトAはすでにスレッド1によってすでにロックされているいるから、デッドロックです。

Objector 14B
なんで、"l_unoWorkingThread"はロックを逆順に行わなければならないわけ?

Special-Student-7
それは、面倒を見なければならない第2点のためです。

スレッドは、それが適切に起こされると保証されるときに限って待ちに入れます。

それは、さもなければ、そのスレッドは際限なく待ち続けることになりかねないからです。

Objector 14B
分かるけど、それが、そのロック順とどう関係するわけ?

Special-Student-7
そのスレッドが"s_unoConnectionUsingThreadsCondition"を対象として待つには、"s_unoConnectionUsingThreadsCondition"がロック済みでなければなりませんが、"s_unoConnectionMaintainingThreadsCondition"もロックされていなければなりません、"s_unoConnectionInformation"をチェックするためには(なぜなら、"s_unoConnectionMaintainingThreadsCondition"が"s_unoConnectionInformation"をプロテクトしているから)。

Objector 14B
なんで、そのスレッドは、まず"s_unoConnectionMaintainingThreadsCondition"して、次に"s_unoConnectionUsingThreadsCondition"をロックしないの?

Special-Student-7
なぜなら、それでは、そのスレッドが待っている間、"s_unoConnectionMaintainingThreadsCondition"がロックされたままになってしまうからです。

Objector 14B
それが問題?

Special-Student-7
勿論。もしもそうなれば、"l_unoConnectingThread"はロックアウトされたままになり、新たなコネクションを確立できなくなります; そうすると、プログラムは永久に固まってしまうでしょう。

Objector 14B
もちろん . . .。それじゃあ、なんで、スレッドは"s_unoConnectionInformation"をチェックして、そして"s_unoConnectionMaintainingThreadsCondition"をリリースして、次に、"s_unoConnectionUsingThreadsCondition"をロックしないの?

Special-Student-7
それは、"s_unoConnectionInformation"は"s_unoConnectionMaintainingThreadsCondition"がリリースされた後、"s_unoConnectionUsingThreadsCondition"がロックされる前に変更されるかもしれないからです、そうすると、チェックは信頼できないものになるでしょう。

Objector 14B
でも、あなたのコードでは、"s_unoConnectionMaintainingThreadsCondition"はスレッドが待ち始める前にどのみちリリース済みじゃない。それはオーケーなわけ?つまり、コネクションは、スレッドが待ち始める前に、既に確立済みかもしれないんじゃない、あなたの理論によれば?

Special-Student-7
それは本当にオーケーなのです、なぜなら、"s_unoConnectionUsingThreadsCondition"がロックされているので、目覚ましコールはまだディスパッチされているはずなく、そこがポイントです。つまり、確かにコネクションが既に確立されているかもしれませんが、それにもかかわらず、スレッドは目覚ましコールを受け取るよう保証されています。

Objector 14B
ああ、デリケートねえ、実際。

Special-Student-7
"l_unoConnectingThread"スレッドについて言えば、それが待ち始める時、コネクションは既に切断されているかもしれませんが、切断はまだ検出されていないと保証されています、したがって、目覚ましコールはまだディスパッチされていないと保証されています。

Objector 14B
ああ、それは、"s_unoConnectionMaintainingThreadsCondition"が"l_unoConnectingThread"スレッドによってロックされていて、コネクション切断検出スレッドは、切断を検出する前に"s_unoConnectionMaintainingThreadsCondition"をロックしなければいけないからね。

目覚ましコールがすでにディスパッチされた後にスレッドが待ち始めないように、あなたは注意を払ってるわけね . . .

Special-Student-7
はい。それが重要な点です、なぜなら、さもなければ、待っているスレッドが予期しているものが来ないということになるでしょう。

そして、面倒を見なければならない第3のポイントは、スレッドは、他のスレッドが走らなければならない時には、待たなければならないということです。

Objector 14B
うん?そうかしら?スレッドが待たなくても別のスレッドはどのみち走れるでしょう、だって、マルチスレッドなんだから?

Special-Student-7
必ずしもそうではありません、実行時環境に依存して。複数のスレッドは本当に同時に走りはしないかもしれません: あるスレッドが走り続けるかもしれません、それが待たない限り。

"l_unoWorkingThread"スレッドは待つ必要はないと思われるかもしれませんが、それは待たなければなりません、コネクション切断検出スレッドが走ると保証されるためには。

Objector 14B
でも、コネクション切断検出スレッドは、"l_unoWorkingThread"が"sleep"してる間に走るでしょう。

Special-Student-7
"sleep"はそこにないと仮定してください: "sleep"がそこにあるのは、絶え間ないメッセージアウトプットが煩わしいというという理由からのみです。

Objector 14B
もしも、プログラムがWebサーバーだったら . . .

Special-Student-7
もしも、プログラムがWebサーバーだったら、それはループではなく、コネクション切断検出スレッドはユーザーリクエストとユーザーリクエストの間に呼び起こされるでしょう。


7: 動かせるサンプル


Special-Student-7
ここに動かせるサンプルがあり、それは、LibreOfficeまたはApache OpenOfficeインスタンスによってローカルに格納されたファイルは、何であろうが自動的にそのセーブファイルを作るものです(注意として、本ケースでは、LibreOfficeまたはApache OpenOfficeインスタンスはローカルであると想定されています)。

そのメインプロジェクトは'connectionAwareExternalUnoClients'であり、プロジェクトをビルドする方法は、以前の記事に記述されています。

プログラムは以下のようにして実行できます。

@bash or CMD ソースコード
gradle i_executeCsharpExecutableFileTask -Pi_commandLineArguments="socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext"

注意として、そのコードは、上記コードのようにフラットに書かれてはいませんが、行なわれていることは同じです、私のユーティリティクラスである、'theBiasPlanet.unoUtilities.processesHandling.UnoProcessEnvironment.UnoProcessEnvironment'、'theBiasPlanet.unoUtilities.connectionsHandling.UnoConnectionConnector.UnoConnectionConnector'、'theBiasPlanet.unoUtilities.connection.UnoConnection.UnoConnection'などの中にしまわれて。   .

Objector 14B
. . . "フラットに"ってどういう意味?

Special-Student-7
上記コードは、説明目的のためにのみ書かれたものです。私は通常そのようには書きません。私は通常、もっと適切に物事をクラス群として構成します。

Objector 14B
"物事をクラス群として構成"?

Special-Student-7
例えば、コネクション確立処理のあの固まりは定型コードであって、それをUNO外部クライアント毎にフラットに書くのは私には嬉しくありません、そこで、私はそれをあるユーティリティクラス内に閉じ込めます; UNOには'com.sun.star.connection.XConnection'および'com.sun.star.bridge.XBridge'というオブジェクトがありますが、その区別は私には実際には役に立つものでなく、そこで、1つのUNOコネクションを全体として表わす単一のクラスを私は持ちます; 特に、UNOは、同一のUNOオブジェクトの様々なUNOプロキシを取り出さないといけないという点において、いくぶん扱いにくいので、各UNOインターフェイスにではなく、UNOコンポーネントに対応するユーティリティクラス('theBiasPlanet.unoUtilities.pointers.UnoObjectPointer')を私は持っており、そのユーティリティクラスにそうした様々なUNOプロキシたちをハンドリングさせます。

Objector 14B
ああ。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>