SDO サービス編

RTC には、サービスポート以外に、SDO サービスと呼ばれるサービスインターフェースを追加することができます。

SDO は Super Distributed Object の略であり、OMG で標準化された分散コンポーネントの規格一つです。 RTC の実態である RTObject は、実は SDO のオブジェクトを継承していて、RTC は SDO のオブジェクトの一種であると言えます。 SDO では、コンポーネントの基本的なインターフェースが定義されています。 SDO のコンポーネントが持つサービスインターフェースは、SDOService インターフェースと呼ばれ、インターフェース定義を継承することになっています。 実は、RTC のポートや実行コンテキストも SDOService を継承しており、SDO サービスの一種となっています。

サービスポートと SDO サービスの違いは何でしょうか?

どちらも、RTC の外側に対してサービスを提供 (Provided) したり、外部のサービスを利用 (Required) するものです。 大きな違いは、サービスポートは RTC の内部のロジック(RTC 開発者が実装するコアロジック)の詳細にアクセスするための(あるいは、コアロジックから外部のサービスにアクセスするための)インターフェースを提供するのに対して、SDO サービスは、RTC 自身、すなわちコアロジックを包含するコンポーネントの機能の詳細にアクセスする(コンポー ネントの機能から外部のサービスにアクセスする)インターフェースを提供します。

  • サービスポート: RTC 内のコアロジックに対するサービス(から利用する)サービス
  • SDOサービス: RTC のコンポーネントとしての機能に対する(から利用する)サービス

SDO サービスの具体的な使われ方は以下のようなものです。

ComponentObserver の例

例えば、OpenRTM の拡張機能として ComponentObserver と呼ばれるものがあります。これは、外部のツールなどが、コンポーネント (RTC) 自身に何らかの状態変化があった際に、ポーリングをしなくとも通知を受け取ることができる仕組みです。

RTC の状態や、プロファイル、EC の状態、ポートの接続・切断を含む状態の変化、コンフィギュレーションの変更などに変更があった場合に、ツール等がその変更の通知を受け取ることができます。

これらの状態変化は、RTC の get_component_profile()、EC の get_profile()関数などを周期的に呼ぶ(ポーリングする)ことで、外部から知ることは可能です。しかし、RTC の様々な変化を知るために、複数のツールや外部の RTC から get_xxx() などの多数の関数を周期的に呼ぶことは非効率であり、変化の見地も最悪ケースではポーリングの周期の分の遅延が発生します。

ツールなどが、あらかじめコールバックオブジェクトをRTCに与えておき、変化があった場合にのみRTC側からそのオブジェクトの関数を即座に呼べば、遅延もなく変化が起きた場合にのみ関数がコールされるため効率的です。

また、こうした機能は RTC のコアロジックとは関係なく、RTC のフレームワークそのものに関連するサービス機能です。したがって、このようなサービスインターフェースは SDO サービスとして実装することが適切です。

なお、ComponentObserver のケースでは、RTC 側ではツールが提供するサービスオブジェクトの関数を呼ぶことで、その機能を実現します。すなわち、サービスの実装はツール側に存在し、RTC 側ではツールのサービスを利用することになります。したがって、このケースでは、RTC 側は SDO サービスのコンシューマ (Required インターフェース) を実装することとなります。

逆に、RTC 側がサービスを提供し、ツールなど外部からそのサービスを利用するケースも考えられます。この場合は、SDOサービスのプロバイダ (Provided インターフェース) を実装することになります。

実現方法

SDO サービスプロバイダ、SDO サービスコンシューマ共に、通常は共有オブジェクトの形で提供され、所定の方法で RTC のプロセスからロード、ファクトリへの登録、インスタンス化されてサービスの提供または利用が開始されます。

SDO サービスは、1つのRTCに対して1種類につき1つの SDO サービスがインスタンス化され対応付けられます。プロセス単位であらかじめ定められたサービスがインスタンス化されます。

rtc.conf に設定可能な SDO サービス関連のオプションは次のようになっています。

SDO サービスプロバイダ関係の設定
sdo.service.provider.available_services 読み出しのみ。利用可能なサービスのリスト
sdo.service.provider.enabled_services 読み込まれた SDO サービスプロバイダのうち、有効にするもの。すべて有効の場合は ALL を指定
sdo.service.provider.providing_services 読み出しのみ。利用されている SDO サービスのリスト。
SDO サービスコンシューマ関係の設定
sdo.service.consumer.available_services 読み出しのみ。利用可能な SDO サービスコンシューマのリスト。
sdo.service.consumer.enabled_services 読み込まれた SDO サービスコンシューマのうち、有効にするもの。すべて有効の場合は ALL を指定

次節からは、SDO サービスの RTC 側でのプロバイダ、コンシューマの実装方法について説明します。

SDO サービスコンシューマ

この節ではSDOサービスのコンシューマの実装方法について説明します。

SDOサービスコンシューマは、ツールなど外部に存在するサービスインターフェースをコールすることで機能するようなサービスを実現する手段です。

前述の ComponentObserver のように、RTC側から何かを通知したり、RTC側で外部のサービスを利用したりする場合にSDOサービスコンシューマを実装します。

SDOサービスの実装

まずはRTCのためのSDOコンシューマを実装する前に、外部にSDOサービスを実装する必要があります。現在は、CORBAのサーバとして、SDOPackage.idlのSDOServiceインターフェースを継承し、通常のCORBAサービスとして実装することになります。

この実装方法は、通常のCORBAサービスの実装の方法となりますので、ここでは割愛します。

SDO サービスコンシューマのライフサイクル

前述のとおり、オブジェクトは通常、共有オブジェクト (so, DLL) としてコンパイル・リンクされます。このオブジェクトがRTCのプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceConsumerFactory に対して登録される。
    • 登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • 外部のツールなどからサービスプロバイダがアタッチされる。
    • この場合、サービスインターフェースの IFR ID が同一である SDO コンシューマがインスタンス化され、提供されたSDOサービスの ServiceProfile (この構造体はサービスのオブジェクトリファレンスを含む) がコンシューマにアタッチされる。
  • このときのアタッチシーケンスは以下の通り。
    1. SDO::get_configuration() により Configuration オブジェクトを取得
    2. Configuration::add_service_profile() により外部側の SdoServiceProvider を ServiceProfile により RTC に与える。
    3. RTC側でサービスを呼び出す必要が有った場合、この SdoServiceConsumer が保持しているサービスオブジェクトプロキシに対して呼び出しを行う
    4. 最終的に SdoServiceConsumer が不要になった場合には、Configuration::remove_service_profile() が id とともに呼び出されSDOサービスコンシューマが RTC から削除される。

   [RTC] [SDO consumer] [Configuration]  [SDO service]    [Other]
     |          :             |                 |            |
     |          :         get_configuration()   |            |
     |<---------:-------------------------------|------------|
     |          :             |                 |            |
     |          :             |   add_service_profile(prof)  |
     |          :  create()   |<----------------|------------|
     |          |<------------|                 |            |
     |          |         call_sdo_service()    |            |
     |          |-------------|---------------->|            |
     |          |         call_sdo_service2()   |            |
     |          |-------------|---------------->|            |
     |          |             |       :         |            |
     |          |             |                 |            |
     |          |             | remove_service_profile(id)   |
     |          |  delete()   |<----------------|------------|
     |          x<------------|                 |            |
     |                        |                 x            x

SDOサービスコンシューマの実装

SDOサービスコンシューマを実装する際には、SdoServiceConsumerBase 基底クラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceConsumerBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceConsumerBase
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceConsumer::init()
  • SdoServiceConsumer::reinit()
  • SdoServiceConsumer::getProfile()
  • SdoServiceConsumer::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile 外部から与えられた SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。外部からSDOサービスが ServiceProfile とともにアタッチされると、SDOコンシューマがインスタンス化され、その直後に SDO サービスがアタッチされた RTC と与えられた ServiceProfile を引数としてこの関数が呼ばれる。

関数内では、ServiceProfile 内の SDO サービスリファレンスを CorbaConsumer クラス等を利用しオブジェクト内に保持するとともに、properties から設定内容を読み込みサービス固有の設定等を行う。与えられたサービスのオブジェクトリファレンスが不正、あるいは properties の内容が不正、等の場合は戻り値に false を返す。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。コンシューマがデタッチされる際に呼び出される関数です。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverConsumerInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceConsumerFactory に対して、当該SDOコンシューマオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)関数(ファンクタ)を登録します。

以下は、典型的なInit() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceConsumerInit()
   {
     RTC::SdoServiceConsumerFactory& factory
       = RTC::SdoServiceConsumerFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>,
                        ::coil::Destructor< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>);
   }
 };

クラス名・ファイル名

SdoServiceConsumer は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceConusmer
  • ファイル名: MySdoServiceConsumer.h. MySdoServiceConsumer.cpp
  • 共有オブジェクト名: MySdoServiceConsumer.so (or DLL)
  • エントリポイント関数名: MySdoServiceConsumerInit()

SDO サービスプロバイダ

この節では SDO サービスのプロバイダの実装方法について説明します。

SDO サービスプロバイダは、自らサービスを外部のツールやアプリケーション・RTC などに対して提供する主体となります。

IDL の定義

SDO サービスプロバイダのライフサイクル

前述のとおり、SDO サービスプロバイダのオブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされます。 このオブジェクトが RTC のプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • オブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされる。
  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceProviderFactory に対して登録される。登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • rtc.conf等のコンフィギュレーション指定により、有効化することが指定されているサービスインプロバイダは、RTC の起動と同時にインスタンス化される。
  • インスタンス化後、初期化関数 init() が呼ばれる。引数には当該サービスのためのコンフィギュレーションオプションが coil::Property により渡される。
  • インスタンス化された SDOサービスプロバイダは SDO::get_sdo_service() により外部からアクセスされる。このとき、サービスを指定する ID は IFR ID と同じである。このときのアタッチシーケンスは以下の通り。
  • RTC が finalize され解体されると同時に SDOサービスプロバイダも解体されるが、その際には SdoServiceProviderBase::finalize() がコールされるので、ここでリソースの解放など終了処理を行う。

   [RTC]      [SDO service]               [Other]
     |              :                        |
     | instantiate  :                        |
     |------------->:                        |
     |    init()    |                        |
     |------------->|                        |
     |              | get_service_profiles() |
     |<--------------------------------------|
     |              |    get_sdo_service()   |
     |<--------------------------------------|
     |              |        use service     |
     |              |<-----------------------|
     |              |                        |
     |  finalize()  |                        |
     |------------->x                        |
     x              x                        |

SDO サービスプロバイダの実装

SDO サービスプロバイダを実装する際には、SdoServiceProviderBase 基底クラスおよび、CORBA サーバントスケルトンクラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceProviderBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceProviderBase,
    
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceProvider::init()
  • SdoServiceProvider::reinit()
  • SdoServiceProvider::getProfile()
  • SdoServiceProvider::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile この SDO サービスプロバイダの SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。このサービスが sdo.service.provider.enabled_services で有効化されていれば、この関数は対応するRTCがインスタンス化された直後に呼び出されます。

ServiceProfile には以下の情報が入った状態で呼び出されます。

ServiceProfile.id 当該サービスのIFR型
ServiceProfile.interface_type 当該サービスのIFR型
ServiceProfile.service 当該サービスのオブジェクト参照
ServiceProfile.properties rtc.conf や <component>.conf 等で与えられた SDOサービス固有のオプションが渡される。confファイル内では、<pragma>.<module_name>.<interface_name> というプリフィックスをつけたオプションとして与えることができ、properties 内には、このプリフィックスを除いたオプションが key:value 形式で含まれている。

関数内では、主に properties から設定内容を読み込みサービス固有の設定等を行います。与えられた ServiceProfileの内容が不正、あるいはその他の理由で当該サービスをインスタンス化しない場合は false を返します。その場合、finalize() が呼び出されその後オブジェクトは削除されます。それ以外の場合は true を返すと、サービスオブジェクトは RTC 内に保持されます。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。RTCオブジェクトが解体されるか、init()処理においてfalseが返された場合は、この関数が呼び出されたのちオブジェクトは解体されます。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverProviderInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceProviderFactory に対して、当該 SDOプロバイダオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)・関数(ファンクタ)を登録します。

以下は、典型的な Init() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceProviderInit()
   {
     RTC::SdoServiceProviderFactory& factory
       = RTC::SdoServiceProviderFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>,
                        ::coil::Destructor< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>);
   }
 };

クラス名・ファイル名

SdoServiceProvider は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceProvider
  • ファイル名: MySdoServiceProvider.h. MySdoServiceProvider.cpp
  • 共有オブジェクト名: MySdoServiceProvider.so (or DLL)
  • エントリポイント関数名: MySdoServiceProviderInit()