2022年5月29日日曜日

67: コネクションアウェアなリモートにも置ける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クライアントを他のプログラミング言語たちで作成することについての記事がありますC#Python)。


本体

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


1: 目標の明確化


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

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

Objector 67A
"scrupulous"は"コネクションアウェア"を意味するのか?

Special-Student-7
コネクションアウェアであることは、1つのハイライトです、それが全てではありませんが、サー。

Objector 67A
他に何がある?

Special-Student-7
最も広く宣伝されている方法は怠惰な方法であり、それは、UNOサーバーはローカルコンピューター内のLibreOfficeまたはApache OpenOfficeインスタンスでなければならないと主張し、頼みもしないのに、ローカルコンピューターにLibreOfficeまたはApache OpenOfficeインスタンスを開始します、そのローカルLibreOfficeまたはApache OpenOfficeインスタンスへ接続する前に。

scrupulousであるというのは、私たちの方法は、その怠惰な方法ではないということを意味しています。

Objector 67A
その"怠惰な方法"は良さそうだな!親切にも、俺のためにLibreOfficeまたはApache OpenOfficeインスタンスを開始してくれるのか?

Special-Student-7
えーと、不愉快な、私の意図への押し付けを別にしても、当該UNOサーバーは、ローカルLibreOfficeまたはApache OpenOfficeインスタンスでないかもしれません。

Objector 67A
じゃあ、何かもしれないというんだ?

Special-Student-7
私のプログラムは、アプリケーションサーバー内のWebアプリケーションかもしれず、クライアントコンピューターたち内のGUIプログラムかもしれず、UNOサーバーは、データサーバー内にいるかもしれない、コネクションアウェア性が便利であり得るシチュエーションです。

Objector 67B
"コネクションアウェア"は具体的には何を意味しているの?つまり、プログラムはどのみち"コネクションアウェア"でしょう、だって、プログラムは、コネクションの使用が否応なく失敗する時にコネクションの切断を知るでしょうに。

Special-Student-7
マダム、コネクションアウェアであるということで私が意味するのは、コネクションの切断が速やかかつ整然と検出されるということです。

あなたの意味でのコネクションアウェア性では、クライアントプログラムのユーザーたちは、画面上で多量のデータを入力し、ボタンを押し、コネクションが切断されていたことを発見し、こう思うということになるかもしれません、"コネクションは切断されていた?いつからだ?もっと早く教えろよ、そうすれは、あのデータを無駄に入力せずに済んだじゃないか!"。

Objector 67B
. . . "整然と"とはどういう意味?

Special-Student-7
各モジュールに、失敗による切断を検出・対処させるのではなく、単一のイベントハンドラに、集中管理で切断を検出・対処させるのが、私にとっての整然とした方法です。

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

Special-Student-7
コネクションを再確立するよう試みることができますが、勿論、問題の原因がまず取り除かれる必要があります、試みが成功するためには。

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

Objector 67B
でも、環境が復旧されたら、コネクションは即座に再確立されるの?

Special-Student-7
そういうサンプルプログラムを後で見ます。

UNOサーバーはLibreOfficeまたはApache OpenOfficeインスタンスであるかもしれないしないかもしれません、しかし、もしも、そうであれば、ご注意いただきたいのですが、LibreOfficeまたはApache OpenOfficeインスタンスはUNOサーバーにしておかないといけません、それがUNOサーバーであるためには。

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

Objector 67B
"UNOサーバーはLibreOfficeまたはApache OpenOfficeインスタンスで〜ないかもしれません"ってどういう意味?他に何であるかもしれないの?

Special-Student-7
実のところ、あなたは、ご自身のUNOサーバーを作成することができます、かなり容易に。


2: 若干の注意


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

特にご注意いただきたいのですが、多分、Microsoft WindowsではVisual C++を使用しなければならないでしょう、その理由は、UNO C++ライブラリはVisual C++でビルドされていることです。


3: コード


Special-Student-7
あれこれ言うのはこれぐらいにして、以下がコードです。

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.hpp

@C++ ソースコード
#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++ ソースコード
#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 67B
. . . 長い!

Special-Student-7
お分かりでしょうが、コードの長さはあなたにとって実際にはほとんど何でもないはずです。確かに、私には何でもなくありませんでしたが、あなたはただそれをコピーすればよいだけです、もしもよろしければ。1行をコピーするのも200行をコピーするのも、あなたの労力にとってたいして違いはありません。

Objector 67B
怠惰な方法っていうのがおお気に入りになるかもしれない、実のところ。

Special-Student-7
コードの長さに恐れをなすというのは、単に感情的、非理性的な反応です。


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


Special-Student-7
そのコードをコンパイルする際に配慮しなければならないことは、UNOインクルードファイル群ディレクトリをインクルードファイル群パスに含めることです。

Objector 67B
そのディレクトリはどこにあるの?

Special-Student-7
実のところ、そのディレクトリはあなたが作らなければなりません。詳細は、Linux用のこの記事またはMicrosoft Windows用のこの記事をお読みください。

Objector 67B
. . . ということは、ヘッダファイル群を私が生成しないといけないということか。

Special-Student-7
そのコードをリンクする際に配慮しなければならないことは、UNOライブラリ群を指定することです。何がそうしたUNOライブラリであるかは、前述の記事をお読みください。


5: コードを実行する


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

第1に、UNOダイナミックライブラリ群ディレクトリパスを適切なオペレーティングシステム環境変数(Linuxでは'LD_LIBRARY_PATH'、Microsoft Windowsでは'PATH')へセットしなければなりません。

Objector 67B
ああ、"UNOダイナミックライブラリ群ディレクトリパス"というのはリンクされたUNOライブラリ群のパスのことね。

Special-Student-7
はい。

第2に、オペレーティングシステム環境変数を、具体的には、'URE_BOOTSTRAP'を、Linuxでは'%オフィスプロダクトディレクトリ%/program/fundamentalrc'の値で、Microsoft Windowsでは'%オフィスプロダクトディレクトリ%/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 67B
. . .それってエラーよね。うまくいってないってことでしょ?

Special-Student-7
予期したとおりに動作しております。UNOサーバーがまだ起動していないというだけのことです。

UNOサーバーを起動させてください。

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

Objector 67B
ふーむ、コネクションが確立された。それ、何をやってるの?

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
UNOサーバーを再び起動させてください。

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

Objector 67B
じゃあ、"コネクションアウェア"プログラムは、無駄に"Doing something"を試みたりしないのね。

Special-Student-7
"Doing something"を無駄に試みるというのは、現実世界においては、ユーザーたちが無駄な作業を行なっているということに相当します。"コネクションアウェア"なプログラムは、そのような無駄な労力を可能な限りユーザーたちがかけなくてよいようにします。

Objector 67B
そのプログラムはどうすれば止められるの?

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

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


6: コードを理解する


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

Objector 67B
はあ?あの長々しいコードを理解する?トリックじゃないの!コード長は重要じゃない、結局のところ!

Special-Student-7
問題は、あなたはそれを何と比較するかということです: 怠惰な方法を選択することと比較すれば、理解せずにただコピーするというのは、より悪いということは全然ありません、なぜなら、あなたはいずれにせよ理解されないのですから。私が申し上げているのは、コピーして理解するというのが最上だということです。

Objector 67B
騙されたわ!

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

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

ご注意いただきたいのですが、プログラムは、UNOサーバーに接続するには、UNOプログラムにならなければなりません、なぜなら、プログラムはUNOサーバーにUNOを手段として接続するからです。

具体的には、"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 67A
じゃあ、プログラムは既にUNOサーバーに接続したわけだ。その後でそれが何故ああした煩わしいことをしないといかんのか理解できんな。

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

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

Objector 67B
はあ?何それ?

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

Objector 67B
何というUNOオブジェクトらなの、具体的には?

Special-Student-7
それは、そのイニシャルUNOオブジェクト群プロバイダが決めることです、しかし、通常は、ローカルUNOオブジェクト群コンテキストです、なぜなら、ローカルUNO環境が操作されるのは通常、ローカルUNOオブジェクト群コンテキストを介してだからです。

Objector 67B
私は私のプログラムを操作されたくないけど、どんな風にも。

Special-Student-7
それでしたら、イニシャルUNOオブジェクト群プロバイダを用意される必要はありません。実際、もしもUNOサーバーがLibreOfficeまたはApache OpenOfficeインスタンスであったら、イニシャルUNOオブジェクト群プロバイダはどのみち使われません。

次に、プログラムは"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 67B
なるほどね。

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

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

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

Objector 67B
ああ、私はその厄介な作業を、ブリッジをつかむためにやらなければならなかったわけね、で、そのブリッジはリスナーを登録するために必要だと。

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

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

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

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

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

Objector 67B
それは分かるけど、コード中のあれらの長々しいコメントが理解できない。

Special-Student-7
マルチスレッドのハンドリングは通常かなりデリケートなものですが、本ケースにおいてもそうです。

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

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

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

Objector 67B
なんで?

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

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

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

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

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

Objector 67B
それがどのようにそのロック順を要求するわけ?

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

Objector 67B
なんで、"s_unoConnectionMaintainingThreadsMutex"は"s_unoConnectionUsingThreadsMutex"より前にロックされてはいけないの?

Special-Student-7
なぜなら、それでは、"s_unoConnectionMaintainingThreadsMutex"は、当該スレッドが待っている間、ロックされ続けることになってしまうからです。

Objector 67B
So? だから?

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

Objector 67B
もちろん . . .。それなら、なぜ、そのスレッドは、"s_unoConnectionInformation"をチェックして"s_unoConnectionMaintainingThreadsMutex"をリリースし、それから"s_unoConnectionUsingThreadsMutex"をロックしないの?

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

Objector 67B
でも、"s_unoConnectionMaintainingThreadsMutex"は、そのスレッドが待ち始める前にリリースされてるでしょう、どのみちあなたのコードでは。あなたの理論によれば、スレッドが待ち始める前にコネクションが既に確立済みということになるかもしれないじゃないの。

Special-Student-7
それは実のところ問題ありません、なぜなら、"s_unoConnectionUsingThreadsMutex"がロックされているので、目覚ましコールはまだディスパッチされているはずなく、そこがポイントです、つまり、コネクションは確かに既に確立されている可能性がありますが、それにもかかわらず、そのスレッドは目覚ましコールを受けると保証されています。

Objector 67B
えーと、そうかしら?

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

それは何故かと言うと、"s_unoConnectionMaintainingThreadsMutex"は"l_unoConnectingThread"スレッドによってロックされており、コネクション切断検出スレッドは、切断を検出する前に、"s_unoConnectionMaintainingThreadsMutex"をロックしなければならないからです。

Objector 67B
じゃあ、あなたは、目覚ましコールが既にディスパッチされた後にスレッドが待ち始めないよう手段を講じてるわけね . . .

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

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

Objector 67B
はあ?それは変よ。その別スレッドはどのみち走れるでしょう、だって、それがマルチスレッディングというものだから。

Special-Student-7
マルチスレッディングは実際にはそうではありません、実のところ。実行時環境によって、同時にはだた1つのスレッドしか走れないかもしれません。したがって、あるスレッドが走り続ければ、別スレッドは止まったままかもしれません。

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

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

Special-Student-7
"sleep"はそこにないものと想定してください: "sleep"がそこにある理由は、ただ、ひっきりなしのメッセージ出力が煩わしいというだけのことです。

Objector 67B
プログラムがWebサーバーだったらどうなの?

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


7: 動かせるサンプル


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

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

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

@bash or CMD ソースコード
gradle i_executeCplusplusExecutableFileTask  -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 67B
"フラットに"?

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プロキシを操作させます。


参考資料


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