RTCプログラミング (応用編)

執筆中 (n-ando)

データポート (応用編)

データポート(基本編)では、データポートの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。

データ型の利用

データポートでは、事前に定義されたデータ型 (例: TimedLong、TimedDouble 等) 以外に、自分で定義したデータ型を使用することもできます。 ただし、自分で新たなデータ型を作る前に、既に似たようなデータ型が定義されていないか確認して、その中に必要なデータ型がない場合にのみ新たなデータ型を定義することをお勧めします。

OpenRTM-aist では、以下の IDLファイルでデータポートに使用するデータ型が定義されています。

  • BasicDataType.idl
  • ExtendedDataTypes.idl
  • InterfaceDataTypes.idl
保存場所は以下のとおりです。これを IDL ディレクトリーと呼びます。
  • UNIX の場合: {prefix}/include/rtm/idl,{prefix}/include/openrtm-x.y/rtm/idl ( {prefix} は /usr/, /usr/local/, /opt/local/ など )
  • Windows の場合: {ProgramFiles}/OpenRTM-aist/x.y/rtm/idl など。( {Program Files} は C:/ProgramFiles, C:/Program Files (x86)など)

独自データ型IDLファイルの作成

データ型を定義する IDLファイルを作成します。データ型は struct キーワードで定義します。以下の基本型や、文字列型、シーケンス型が利用できます。

意味 宣言例
short short型整数 short shortVariable;
long long型整数 long longVariable;
unsinged short short型整数 unsigned short ushortVariable;
unsigned long long型整数 unsigned long ulongVariable;
float 単精度浮動小数点 float floatVariable;
double 倍精度浮動小数点数 double doubleVariable;
char 文字型 char charVariable;
wchar wchar文字型 char charVariable;
boolean bool型 bool shortVariable;
octet octet型 octet octetVariable;
longlong longlong型整数 longlong longlongVariable;
ulonglong unsinged longlong型整数 ulonglong ulonglongVariable;
sequence<T> シーケンス型 sequence<double> doubleSeqVariable;

ここでは、MyDataType.idl に MyData というデータ型を定義することにします。 表示のためにスペースを前方に入れていますが、これは実際にIDLファイルを使用する上で不要のため削除してください。

 // @file MyDataType.idl
 #include "BasicDataType.idl"
 
 module MyModule
 {
  struct MyData
  {
    RTC::Time tm;
    short shortVariable;
    long longVariable;
    sequence<double> data;
  };
 };

2行目に

 #include "BasicDataType.idl"
とあるのは、MyData 型の一番最初のフィールド tm (RTC::Time 型) を利用するために必要です。 特に理由がない場合、独自データ型でも、一番初めのフィールドはタイムスタンプを格納するために RTC::Time tm; と宣言してください。 また、module MyModule はネームスペースの指定です。データ型を定義する際は必ず適当なネームスペースを定義しその中でデータ型を定義してください。

RTC Builderでプロジェクト作成

RTCBuilder でプロジェクトの作成を行います。

「RTC Builder Project」のアイコンをクリックし、表示したウィンドウにプロジェクト名を入力し「終了」すると、左のウィンドウに生成したプロジェクトファイルが表示します。

idl_00000.png
「RTC Builder Project」ウィンドウを表示

idl_002.png
idlフォルダーの確認

そこに独自データ型を配置するidlフォルダーがあるので独自データ型のIDLファイル(MyDataType.idl)を、ドラッグ&ドロップや[右クリック]>[貼り付け]などで置いてください。

プロジェクトフォルダーは、RTCBuilder起動直後に表示する「ディレクトリをワークスペースとして選択」をデフォルトで進めた場合、[ c:\Users\ユーザ名\workspase ]フォルダー以下にあります。

データポートの作成

RTCBuilder で独自データ型のidlを使ったコンポーネントの作成を行います。

データポート設定タブを開き、データポート(InPort/OutPort)を定義します。

作成したデータポートで独自データ型を使いたい場合、[Reload] をクリックするとidlフォルダーのMyDataType.idlが読み込まれ、 *データ型 のプルダウンで新たに定義した MyData を選択できます。

idl_000.png
[Reload] をクリック、MyData のデータ型を選択

その他、コンポーネント作成に必要な項目の設定が終わったら、基本タブに戻り、[コード生成] ボタンをクリックし、コードの生成を行います。

データポート、コネクタのコールバックの利用

InPort は isNew() でデータの到着の有無を確認して、read() で読みだす、あるいは OutPort は write() でデータを送り出す、ということについてはすでに述べました。

例えば、InPort はデータが来てから、onExecute() 等などの関数内で、isNew() を呼び read() を呼び出すまでデータを取得することはできません。 onExecute() の周期が非常に速かったとしても、データが InPort に到着するタイミングと、実際に処理が行われるタイミングは非同期に行われます。

データが到着してすぐに、すなわち同期的に処理を行いたい場合にはどうすればよいのでしょうか。これを実現する方法として、OpenRTM-aist ではデータポートやコネクタの種々の処理のタイミングで呼び出されるコールバックを定義しています。

コールバックには大きく分けて、1) InPort、2) OutPort、3) コネクタ、4) ポート の4種類のコールバックが用意されています。

InPort のコールバック

InPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnRead InPort の read() が呼び出された際にコールされる InPort::setOnRead() 関数でセット。
OnReadConvert InPort の read() が呼び出された際にデータを変換するためにコールされる。InPort::setOnReadConvert() 関数でセット。

OnRead コールバックは read() が呼び出されたときに、OnReadConvert は read() が呼び出されたとき、呼び出し元にある種の変換を施したデータを返すために使用するコールバックです。

それぞれのコールバックは、rtm/PortCallback.h で定義されているそれぞれのファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };

OnRead を継承した MyOnRead ファンクタでは、コンストラクタで出力ストリーム std::ostream を渡しています。どこかでオープンしたファイル出力ストリーム std::ofstream 等を渡すことを意図しています。 ファンクタの実体である operator()() では、出力ストリームと標準出力に対して、文字列を出力しています。このように、ファンクタでは、予めコンストラクタなどで状態変数を渡すことで、他のオブジェクトに対する呼び出しも実現することができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(constT&) のみを実装しています。この関数の引数には、read() が呼ばれたときにInPort 変数に読みだされる前のデータが渡されます。 この関数内で何らかの処理を行い return で返したデータは InPort 変数に書き込まれます。この例では、データ型に data というメンバがあり、かつ乗算演算子が定義されているという前提で自乗を計算して返しています。 適切なメンバがない変数型を使用すればコンパイルエラーになります。

さて、このファンクタを実際にコンポーネントに組み込んでみましょう。InPort を使用しているサンプルとして、ここでは OpenRTM-aist に含まれているサンプルである ConsoleOut を利用します。ConsoleOut は OpenRTM-aist のソースを展開すると、

 OpenRTM-aist-<version>/examples/SimpleIO/

の下に、また Linux 等でパッケージ等からインストールすると、

 /usr/share/OpenRTM-aist/examples/src/

の下にソースコードがあります。

まず、上記のクラス定義を、ConsoleOut.h に記述します。クラス定義は、本来別のソースに記述した方が良いのですが、ファンクタクラスは、このコンポーネント内でしか使用せず、内容も短いものですので、こういう場合はヘッダ内で実装も含めて定義しても構わないでしょう。

 // ConsoleOut.h
 
   中略
 // Service Consumer stub headers
 // <rtc-template block="consumer_stub_h">
 
 // </rtc-template>
 
 using namespace RTC; 
 
 // ここから追加分
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };
 // ここまで追加分
 
 class ConsoleOut
   : public RTC::DataFlowComponentBase
 {
 
   中略
 
  protected:
   // DataInPort declaration
   // <rtc-template block="inport_declare">
   TimedLong m_in;
   InPort<TimedLong> m_inIn;
 
   中略
 
  private:
   //ここから追加分
   MyOnRead<TimedLong>* m_onread;
   MyOnReadConvert<TimedLong>* m_onreadconv;
   //ここまで追加分
 };

まず、ConsoleOut クラスの宣言の前に、コールバックファンクタ MyOnRead とMyOnReadConvert を宣言します。 これらのクラスのポインタ変数をメンバとして持たせるために、private の部分に、それぞれのポインタ変数を宣言します。 このとき、MyOnRead/MyOnReadConvert ともに、クラステンプレートの型引数にこのコンポーネントの InPort の型と同じ、TimedLong を与えていることに注意してください。

OutPort のコールバック

OutPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnWrite OutPort の write() が呼び出された際にコールされる OutPort::setOnWrite() 関数でセット。
OnWriteConvert OutPort の write() が呼び出された際にデータを変換するためにコールされる。OutPort::setOnWriteConvert() 関数でセット。

OnWrite コールバックは write() が呼び出された際に、OnWriteConvert はwrite() が呼び出された際に、ある種の変換を施したデータを送信するために使用するコールバックです。

それぞれのコールバックは、InPort と同様 rtm/PortCallback.h で定義されているそれぞれのファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnWrite
  : public RTC::OnWrite<T>
 {
 public:
   MyOnWrite(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "write() 関数が呼ばれました。" << std::endl;
     std::cout << "write() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnWriteConvert
  : public RTC::OnWriteConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = 2 * value.data;
     return tmp;
   }
 };

コールバック用のファンクタの書き方は、InPort の OnRead/OnReadConvert とほぼ同じです。OnWrite を継承した MyOnWrite ファンクタでは、コンストラクタで出力ストリーム std::ostream を渡しています。 どこかでオープンしたファイル出力ストリーム std::ofstream 等を渡すことを意図しています。ファンクタの実体である operator() では、出力ストリームと標準出力に対して、文字列を出力しています。 このように、ファンクタでは、予めコンストラクタなどで状態変数を渡すことで、他のオブジェクトに対する呼び出しも実現することができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(constT&) のみを実装しています。この関数の引数には、read() を呼んだときに InPort 変数に読みだされる前のデータが渡されます。この関数内で何らかの処理を行い return で返したデータは InPort 変数に書き込まれます。 この例では、データ型に data というメンバがあり、かつ乗算演算子が定義されているという前提で自乗を計算して返しています。適切なメンバがない変数型を使用すればコンパイルエラーになります。

コネクタ・バッファのコールバック

コネクタ

コネクタはバッファおよび通信路を抽象化したオブジェクトです。図に示すように、OutPort と InPort の間に存在し、OutPort からは write() 関数によりデータの書き込み、InPort からは read() 関数によりデータの読み出しが行われます。 コネクタは、データがどのような手段で OutPort から InPort へ伝送されるかを抽象化し隠蔽します。

OutPort はコネクタ内のバッファに対して、
  • 書き込み
  • 各種制御 (読み戻し、未読データへのアクセス等)
  • バッファフル状態の通知およびタイムアウトの通知を行う(または通知を受ける)ことができます。
  • データの読み出し
  • 各種制御(読み戻し、未読データへのアクセス等)
  • バッファエンプティ状態の通知およびタイムアウト通知を行う(または通知を受ける)ことができます。

OutPort は複数の InPort へ接続することができますが、一つの接続につき、一つのコネクタが生成されます。(実際には InPort も複数の接続を同時に持つこともできますが、データを区別する方法がないので、通常は用いません。) つまり、接続が3つあれば、コネクタが3つ存在し、それぞれに対して書き込みのステータスが存在することになります。

また、これらの機能のために、OutPort/InPort 一対に対して、それぞれ一つコネクタが存在する必要があることがわかります。 さらに、コネクタをサブスクリプション型に対応した実装レベルでモデル化するにあたり、パブリッシャと呼ばれる非同期通信のためのオブジェクトを導入しました。

データポートは接続が確立されると、1つの接続につき1つのコネクタオブジェクトを生成します。コネクタは、OutPort と InPort をつなぐデータストリームの抽象チャネルです。

ON_BUFFER_WRITE バッファ書き込み時
ON_BUFFER_FULL バッファフル時
ON_BUFFER_WRITE_TIMEOUT バッファ書き込みタイムアウト時
ON_BUFFER_OVERWRITE バッファ上書き時
ON_BUFFER_READ バッファ読み出し時
ON_SEND InProtへの送信時
ON_RECEIVED InProtへの送信完了時
ON_RECEIVER_FULL InProt側バッファフル時
ON_RECEIVER_TIMEOUT InProt側バッファタイムアウト時
ON_RECEIVER_ERROR InProt側エラー時
ON_BUFFER_EMPTY バッファが空の場合
ON_BUFFER_READTIMEOUT バッファが空でタイムアウトした場合
ON_SENDER_EMPTY OutPort側バッファが空
ON_SENDER_TIMEOUT OutPort側タイムアウト時
ON_SENDER_ERROR OutPort側エラー時
ON_CONNECT 接続確立時
ON_DISCONNECT 接続切断時

ポートのコールバック

ステータス

データポートは、データの送受信を行った際に、ステータスを返します。 ステータスは、rtm/DataPortStatus.h で定義されています。

PORT_OK 正常終了
PORT_ERROR 異常終了
BUFFER_ERROR バッファエラー
BUFFER_FULL バッファフル
BUFFER_EMPTY バッファエンプティ
BUFFER_TIMEOUT バッファタイムアウト
SEND_FULL データを送信したが相手側がバッファフル状態
SEND_TIMEOUT データを送信したが相手側がタイムアウトした
RECV_EMPTY データを送信したがデータが空状態
RECV_TIMEOUT データを受信しようとしたがタイムアウトした
INVALID_ARGS 不正な引数
PRECONDITION_NOT_MET 事前条件を満たしていない
CONNECTION_LOST 接続が切断された
UNKNOWN_ERROR 不明なエラー
データポートのデータ経路上のエラー発生個所から呼び出し側へエラー情報を伝えるためにこのエラーコードを使用します。 主に、伝送路上のエラー、伝送先のエラーなどが考えられますが、各部分で発生するエラーを以下に示します。
  • Push 型
    • InPortConsumer と Publisher/Activity 間で発生するリターンコード
      PORT_OK, PORT_ERROR, SEND_FULL, SEND_TIMEOUT, CONNECTION_LOST, UNKNOWN_ERROR
    • Activity と OutPort の Buffer/Connector 間で発生するリターンコード
      PORT_OK, PORT_ERROR, BUFFER_ERROR, BUFFER_FULL, BUFFER_TIMEOUT, UNKNOWN_ERROR
  • Pull 型
    • Activity と InPort の間で発生するリターンコード
      PORT_OK, PORT_ERROR, RECV_EMPTY, RECV_TIMEOUT, CONNETION_LOST, UNKNOWN_ERROR

独自データポートインターフェースの作成

独自データポートインターフェースの作成例を示します。

以下の関数、クラスの定義が必要です。

  • プロバイダクラス (InPortTestProvider)
  • コンシューマクラス (InPortTestConsumer)
  • プロバイダ、コンシューマ登録関数 (InPortTestInterfaceInit)

プロバイダクラス (InPortTestProvider)

データポートインターフェース(Push型)のプロバイダクラスです。 コンシューマ側で put関数を呼び出した際に、何らかの方法によりプロバイダ側にデータを転送する必要があります。 このサンプルではコンシューマ側の put関数呼び出し時にファイルにデータを書き込み、プロバイダ側でファイルからデータを読み込むことでデータの転送を行っています。

 //InPortTestProvider.cpp
 
 #include "InPortTestProvider.h"
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
   InPortTestProvider::InPortTestProvider(void)
      : m_buffer(0), m_running(true), m_filename("data.dat")
  {
    setInterfaceType("test");
    activate();
  }
  
  InPortTestProvider::~InPortTestProvider(void)
  {
      m_running = false;
      wait();
  }
 
  //プロバイダ生成時に呼び出される関数
  //コネクタプロファイル、ポートのプロパティの情報を受け取る
  void InPortTestProvider::init(coil::Properties& prop)
  {
  }
 
  void InPortTestProvider::
  setBuffer(BufferBase<cdrMemoryStream>* buffer)
  {
    m_buffer = buffer;
  }
 
  void InPortTestProvider::setListener(ConnectorInfo& info, ConnectorListeners* listeners)
  {
    m_profile = info;
    m_listeners = listeners;
  }
 
  void InPortTestProvider::setConnector(InPortConnector* connector)
  {
    m_connector = connector;
  }
 
  //別スレッドにより実行される関数
  //周期的にファイルからデータを読み込んでバッファに書き込む
  //この関数はこのサンプルでは必要ですが、独自インターフェースを作成するうえで
  //必須ではありません
  int InPortTestProvider::svc()
  {
     coil::sleep(1);
     while (m_running)
     {
      std::ifstream  fin;
      fin.open(m_filename, std::ios::in | std::ios::binary);
          if (fin)
          {
               while (!fin.eof())
               {
                    int data_size = 0;
                    fin.read((char*)&data_size, sizeof(int));
                    if (data_size > 0)
                    {
                        CORBA::OctetSeq data;
                        data.length(data_size);
                        fin.read((char*)&data[0], data_size);
 
                        //cdrMemoryStream型変数にデータを格納してバッファに書き込む
                        //以下の記述方法はomniORB特有なため、TAOやORBexpressに対応する場合は
                        //分ける必要がある
                        cdrMemoryStream cdr;
                        //エンディアンの設定を行う
                        bool endian_type = m_connector->isLittleEndian();
                        cdr.setByteSwapFlag(endian_type);
                        //データを書き込む
                        cdr.put_octet_array(&(data[0]), data.length());
                        //バッファに書き込む
                        m_buffer->write(cdr);
                    }
               }
               fin.close();
      }
     }
     return 0;
  }
 
  //コネクタ接続時に呼び出される関数
  //この関数はコンシューマ側のsubscribeInterface関数よりも前に呼び出される
  //このため、publishInterface関数で設定した情報をコンシューマ側のsubscribeInterface関数で
  //取得することができる
  //何か問題があった時はfalseを返してコネクタを切断する
  bool InPortTestProvider::
      publishInterface(SDOPackage::NVList& properties)
   {
        //データを書き込むファイル名の情報を格納する
        CORBA_SeqUtil::
                  push_back(properties,
                  NVUtil::newNV("dataport.test.filename", m_filename.c_str()));
    return true;
   }
 };
 
 extern "C"
 {
     //この関数をモジュールロード時に呼び出す必要がある
    void InPortTestProviderInit(void)
    {
         RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance());
         factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortProvider,
                                        ::RTC::InPortTestProvider>,
                       ::coil::Destructor< ::RTC::InPortProvider,
                                           ::RTC::InPortTestProvider>);
     }
 };


データポートインターフェース(Push型)のコンシューマクラスです。InPortProvider を継承する必要があります。 Taskクラスの継承はこのサンプル独自のものなので必須ではありません。

 //InPortTestProvider.h
 
 #ifndef RTC_INPORTTESTPROVIDER_H
 #define RTC_INPORTTESTPROVIDER_H
 #include <rtm/BufferBase.h>
 #include <rtm/InPortProvider.h>
 #include <rtm/CORBA_SeqUtil.h>
 #include <rtm/Manager.h>
 #include <rtm/ConnectorListener.h>
 #include <rtm/ConnectorBase.h>
 #include <fstream>
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
    class InPortTestProvider
      : public InPortProvider,
    public coil::Task
    {
    public:
       InPortTestProvider(void);
       virtual ~InPortTestProvider(void);
       virtual void init(coil::Properties& prop);
       virtual void setBuffer(BufferBase<cdrMemoryStream>* buffer);
       virtual void setListener(ConnectorInfo& info,
                             ConnectorListeners* listeners);
       virtual void setConnector(InPortConnector* connector);
       virtual bool publishInterface(SDOPackage::NVList& properties);
       virtual int svc();
 
  private:
      CdrBufferBase* m_buffer;
      ConnectorListeners* m_listeners;
      ConnectorInfo m_profile;
      InPortConnector* m_connector;
      bool m_running;
      std::string m_filename;
   }; 
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestProviderInit(void);
 };
 
 #ifdef WIN32
 #pragma warning( default : 4290 )
 #endif
 #endif


コンシューマクラス (InPortTestConsumer)

データポートインターフェース(Push型)のコンシューマクラスです。Push型の場合は InPort側にコンシューマ、OutPort側にプロバイダを生成します。 コンシューマ側で put関数を呼び出した際に、何らかの方法によりプロバイダ側にデータを転送する必要があります。 このサンプルではコンシューマ側の put関数呼び出し時にファイルにデータを書き込み、プロバイダ側でファイルからデータを読み込むことでデータの転送を行っています。

 //InPortTestConsumer.cpp
 
 #include <rtm/NVUtil.h>
 #include "InPortTestConsumer.h"
 
 namespace RTC
 {
    InPortTestConsumer::InPortTestConsumer(void)
    : rtclog("InPortTestConsumer")
    {
    }
  
    InPortTestConsumer::~InPortTestConsumer(void)
    {
       RTC_PARANOID(("~InPortTestConsumer()"));
    }
 
    //コネクタプロファイル、ポートのプロパティの情報を受け取る
    void InPortTestConsumer::init(coil::Properties& prop)
    {
       m_properties = prop;
    }
  
    //データ転送時に呼び出される関数
    //put関数内でプロバイダ側にデータを転送する処理を記述する
    InPortConsumer::ReturnCode InPortTestConsumer::
      put(const cdrMemoryStream& data)
    {
         RTC_PARANOID(("put()"));
 
         //このサンプルでは、コンシュマー側でファイルにデータを書き込んで、プロバイダ側で
         //ファイル内のデータを読み込むことにしている
         //バイナリファイルを開く
         m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
         //データサイズと生データをファイルに書き込む
         int data_size = data.bufSize();    
         m_file.write((char*)&data_size, sizeof(int));
         m_file.write((char*)data.bufPtr(), data_size);
         //ファイルを閉じる
         m_file.close();
 
         return PORT_OK;
      }
 
      //コネクタ接続時に呼び出される関数
      //この関数はプロバイダ側のpublishInterface関数よりも後に呼び出される
      //このため、プロバイダ側のpublishInterface関数で設定した情報を取得することができる
      //return: 何か問題があった時はfalseを返してコネクタを切断する
 
      bool InPortTestConsumer::
      subscribeInterface(const SDOPackage::NVList& properties)
      {
          //プロバイダ側で設定したファイル名を取得する
          CORBA::Long index = NVUtil::find_index(properties,
                                          "dataport.test.filename");
          const char* filename(0);
          properties[index].value >>= filename;
 
          //取得したファイル名のファイルを開く
          m_filename = filename;
          m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
          m_file.close();
  
         return true;
     }
  
     //コネクタ切断時に呼び出される関数
     void InPortTestConsumer::
     unsubscribeInterface(const SDOPackage::NVList& properties)
     {
     }
 
     void InPortTestConsumer::publishInterfaceProfile(SDOPackage::NVList& properties)
     {
     }
 };
 
 extern "C"
 { 
     //コンシューマ登録関数
     //この関数をモジュールロード時に呼び出す必要がある
     void InPortTestConsumerInit(void)
    {
        RTC::InPortConsumerFactory& factory(RTC::InPortConsumerFactory::instance());
        factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortConsumer,
                                        ::RTC::InPortTestConsumer>,
                       ::coil::Destructor< ::RTC::InPortConsumer,
                                           ::RTC::InPortTestConsumer>);
     }
 };


データポートインターフェース(Push型)のコンシューマクラスです。InPortConsumer を継承する必要があります。

 //InPortTestConsumer.h
 
 #ifndef RTC_INPORTTESTCONSUMER_H
 #define RTC_INPORTTESTCONSUMER_H
 #include <rtm/InPortConsumer.h>
 #include <rtm/Manager.h>
 #include <fstream>
 
 namespace RTC
 {
     class InPortTestConsumer
       : public InPortConsumer
    {
       public:
        DATAPORTSTATUS_ENUM
        InPortTestConsumer(void);
        virtual ~InPortTestConsumer(void);
        virtual void init(coil::Properties& prop);
        virtual ReturnCode put(const cdrMemoryStream& data);
        virtual void publishInterfaceProfile(SDOPackage::NVList& properties);
        virtual bool subscribeInterface(const SDOPackage::NVList& properties);
        virtual void unsubscribeInterface(const SDOPackage::NVList& properties);
 
      private:
        mutable Logger rtclog;
        coil::Properties m_properties;
        std::ofstream  m_file;
        std::string m_filename;
   };
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestConsumerInit(void);
 };
 #endif


プロバイダ、コンシューマ登録関数 (InPortTestInterfaceInit)

OpenRTM-aist のマネージャは「XXX.dll」というダイナミックリンクライブラリをロードした場合に「XXXInit」関数を呼び出します。 このサンプルの場合は「InPortTestInterface.dll」をロードして、以下の「InPortTestInterfaceInit」関数を呼び出します。

 //InPortTestInterface.cpp
 
 #include "InPortTestConsumer.h"
 #include "InPortTestProvider.h"
 
 extern "C"
 {
    DLL_EXPORT void InPortTestInterfaceInit(RTC::Manager* manager)
    {
        InPortTestProviderInit();
        InPortTestConsumerInit();
    }
 };

確認手順

1. OpenRTM-aistがインストールされている環境を用意します。

2. ビルドのために CMake設定ファイル (CMakeLists.txt) を作成します。
以下は、独自インターフェースのサンプルをビルドするための CMake設定ファイルです。

 //OpenRTM-aistのライブラリを見つけるための記述
 
 cmake_minimum_required (VERSION 2.6)
 
 find_package(OpenRTM HINTS /usr/lib64/openrtm-1.1/cmake)
 if(${OpenRTM_FOUND})
   MESSAGE(STATUS "OpenRTM configuration Found")
 else(${OpenRTM_FOUND})
   message(STATUS "Use cmake/Modules/FindOpenRTM.cmake in the project")
   list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
   find_package(OpenRTM REQUIRED)
 endif(${OpenRTM_FOUND})
 
 if (DEFINED OPENRTM_INCLUDE_DIRS)
   string(REGEX REPLACE "-I" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
 endif (DEFINED OPENRTM_INCLUDE_DIRS)
 
 if (DEFINED OPENRTM_LIBRARY_DIRS)
   string(REGEX REPLACE "-L" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
 endif (DEFINED OPENRTM_LIBRARY_DIRS)
 
 if (DEFINED OPENRTM_LIBRARIES)
   string(REGEX REPLACE "-l" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
 endif (DEFINED OPENRTM_LIBRARIES)
 
 include_directories(${OPENRTM_INCLUDE_DIRS})
 include_directories(${OMNIORB_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 add_definitions(${OMNIORB_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 link_directories(${OMNIORB_LIBRARY_DIRS})
 
 //プロジェクト名設定
 project (InPortTestInterface)
 
 //動的ライブラリを作成する
 add_library(InPortTestInterface SHARED InPortTestProvider.cpp InPortTestConsumer.cpp InPortTestProvider.h InPortTestConsumer.h InPortTestInterface.cpp)
 
 //リンクするライブラリの設定
 target_link_libraries(InPortTestInterface ${OPENRTM_LIBRARIES})

3. CMake により Visual Studio のプロジェクトファイルを生成します。

4. Visual Studio でビルドします。
ビルド後に、Debugフォルダーに InPortTestInterface.dll が生成されます。

5. rtc.conf を作成します
rtc.conf を作成して、Manager 起動時にロードするモジュールで InPortTestInterface.dll を指定します。

 manager.modules.preload: InPortTestInterface.dll

InPortTestInterface.dll が、RTC を実行するディレクトリーと異なる場合は、別途モジュール探索パスを設定します。

 manager.modules.load_path: C:¥workspace¥InPortTestInterface¥build¥Debug

6. 上記の rtc.conf を読み込んで RTCを 起動します。
 ConsoleInComp.exe -f ../rtc.conf

7. ポート接続時にInterface Typeに「test」を指定する。
RTSystemEditor上から Interface Type を指定できます。

サービスポート (応用編)

IDL 文法

構造体 / オブジェクトリファレンス

CORBA の構造体は、以下のように C++ の構造体 "struct" にマッピングされます。

 struct Profile
 {
    short short_value;
    long  long_value;
 };

 // -*- C++ -*-
 struct Profile
 {
    CORBA::Short short_value;
    CORBA::Long  long_value;
 };

 class Profile_var
 {
  
 };

  • 固定長の構造体と可変長の構造体
    下記に示す、構造体に可変長のメンバーが含まれていると、構造体は可変長データとみなされます。 この場合、固定長の構造体とは異なる C++ コードが生成され、戻り値や out パラメーターにおける扱いが異なります。
    • 制限付き文字列または無制限文字列
    • 制限付きシーケンスまたは無制限シーケンス
    • 可変長メンバーを含む構造体またはユニオン
    • 可変長の要素型を持つ配列
    • 可変長型へのtypedef

上記の一覧に型が該当しない場合、その型は固定長になります。

  • _var型 IDLコンパイラは IDLにおける構造体から、C++ の構造体と _var 型のクラスを自動的に生成します。 _var 型クラスはスマートポインタのように振る舞い、可変長の構造体として _var クラスを使用する場合、可変長のメンバーに割り当てられるメモリーは自動的に管理されます。 _var 型の変数(var1)から他の_var型の変数(var2)に代入が行われた場合には、そのポインタの所有権が var2 に移り、その後 var1 は初期化もしくは代入が行われるまで使用することはできません。

  • 構造体がスコープの外に出ると,可変長のメンバーに関連付けられていたすべてのメモリーは自動的に解放されます。 ただし、すでに所有権を他に譲渡している_var型変数がスコープから出る場合、所有権を既に譲渡しているので、元のデータに対しては開放は行われません。

  • 初期化もしくは代入された構造体に対して、再び初期化または代入が行われた場合、元のデータに関連付けられていたメモリーは自動的に解放されます。

  • 可変長のメンバーにオブジェクトリファレンスが代入される場合は、必ずそのオブジェクトリファレンスのコピーが作成されます。可変長のメンバーにポインタが代入される場合、コピーは作成されません。

_var型

変換コンストラクタ(T_ptr)

  • 実装例

 _CORBA_ObjRef_Var(T_ptr p) : pd_objref(p) {}

_ptr型のオブジェクト参照の所有権は_var型に移るため、参照カウントは増減しません。

 MyObject_ptr ptr = obj->get_myobject();
 // ptr は適切なタイミングで release されなければならない
 
 MyObject_var var(ptr); 
 // 所有権は var に移ったため、ptr をリリースする必要はない
 // var がスコープを抜けるなどして解体されると、参照カウントは release される
 

変換コンストラクタ(const T_var&)

  • 実装例

 Object_var(const T_var& p) : pd_ref(T::_duplicate(p.pd_ref)) {}

代入元の_var型のオブジェクト参照の所有権はコピーされます。

 MyObject_var var0 = obj->get_myobject();  // var0 は所有権を持つ
 MyObject_var var1(var0);  // リファレンスカウントはインクリメントされ var1も所有権を持つ

変換コンストラクタ(const T_member)

  • 実装例

in引数と in()関数

単純にオブジェクト参照の_ptr型ポインタを返します。所有権は移行されません。

  • 実装例

 T_ptr  in() const { return pd_objref; }

通常、関数の in 引数にオブジェクト参照を渡す際に使用します。 オブジェクト参照を与えられた側の関数は、所有権を持たないため関数内で release してはいけません。 関数から戻ってきたあと、_var型変数に依然として所有権が保持されており、_var型変数の解体時にオブジェクトは release されます。

オブジェクト参照を in 引数として受け取る関数を定義する場合は、_ptr型の引数として定義します。 さらに、関数内では、そのオブジェクトのオペレーションを呼ぶだけで、release 等は行ってはいけません。 また、関数内でオブジェクト参照をどこか(グローバル変数、static変数、オブジェクトのメンバー等)に保存したい場合は、所有権をコピーするため duplicate関数で複製する必要があります。

  • 使用例

 void myfunc(MyObject_ptr obj)
 {
    obj->function();
    CORBA::release(obj); // ×これはしてはいけない
    m_obj = obj; // ×所有権を持っていない
    m_obj = MyObject::duplicate(obj); // ○所有権を複製
 }
 
 {// 別のコンテキスト 
    MyObject_var obj(hoge->get_object());
    myfunc(obj.in()); // 所有権は移行されない
 }
 // スコープを抜けたので参照カウントがデクリメントされる

out引数と out()関数

現在所有している参照を release して、リファレンスのポインタを nil にセットして返します。 すなわち、out()関数呼び出し以前にもしオブジェクト参照を保持している場合は、その所有権を放棄し参照は破棄されます。

  • 実装例

 T_ptr& out()
 {
    T_Helper::release(pd_objref);
    pd_objref = T_Helper::_nil();
    return pd_objref;
 }

通常、関数の out引数にオブジェクト参照を渡す際に使用します。 すなわち、関数から戻ってきたあと、この変数に新たにオブジェクト参照が保持されていることが期待されます。 このとき、オブジェクト参照の所有権はこの変数に保持されていると考えます。 out() で変数を渡された関数側では、何らかの形でオブジェクト参照を生成または複製して、引数に所有権を渡す必要があります。

オブジェクト参照を out引数として受け取る関数を定義する場合は、_ptr型参照の引数として定義します。 関数内では、引数は必ずnilオブジェクト参照であり、かつ通常何らかのオブジェクトを所有権を与えて代入することが期待されます。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        assert(CORBA::is_nil(obj)); // 必ず nil オブジェクトを指定する
        obj->function(); // nil なのでオペレーションは呼べない
     
        // m_obj は_var型のメンバ変数
        obj = m_obj; // ×所有権を複製していない。
        // return後、関数の外で勝手に release されるかもしれない。
     
        obj = MyObject::duplicate(obj); // ○所有権を複製している。
        // return後、関数の外で release されても、オブジェクト参照は解体されない。
        return;
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // △out変数として使うので渡す前には何も入れない方がよい
        myfunc(obj.out());
        // オブジェクトが代入され返ってきたはず
        assert(!CORBA::is_nil(obj));
        obj->function();
     }
     // スコープを抜けたので参照カウントがデクリメントされる。
     

    inout()

リファレンスへのポインタの参照を返します。

  • 実装
     T_ptr& inout()    { return pd_objref; }

通常、関数の inout引数にオブジェクト参照を渡す際に使用します。 すなわち、関数内では何らかのオブジェクト参照が引数に入っていることが期待され、オブジェクトの所有権は関数側に移行します。 また、関数は何らかのオブジェクト参照をこの引数に与えて返すことが期待され、引数すなわち呼び出し元の変数に所有権を与えます。 関数内では引数にオブジェクト参照を新たにセットする場合は、まず release してから新たなオブジェクト参照を生成または複製して引数に所有権を渡す必要があります。

オブジェクト参照を inout引数として取る関数は、設計の観点からあまり推奨されません。 もし、inout引数として取る関数を定義する必要がある場合は、_ptr型参照の引数として定義します。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        if (!CORBA::is_nil(obj))
        {
            obj->function(); // obj が nil でなければオペレーションを呼ぶことができる。
        }
     
        CORBA::release(obj); // releaseする責任はこの関数にある
        /*
        * この関数内で、obj に新たなオブジェクト参照がある場合に限り、
        * 引数を受け取った直後に、_var変数に代入しておくことで、
        * 関数終了時に自動的に参照カウントをデクリメント
        * するテクニックを使用してもよい。
        * MyObject_var deleter = obj;
        */
     
        // MyObject_var m_obj とする
        obj = m_obj; // × 所有権を複製していない
        // return 後、関数の外で release されるかもしれない。
        obj = MyObject::_duplicate(m_obj); // ○ obj にも MyObject の所有権が与えられた
        // return 後、関数の外で release されても、オブジェクト参照は解体されない。
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // obj は所有権を持っている
        myfunc(obj); // 関数内で releae される
        // obj の指すものは入れ替わっているかもしれない。
     }
     // スコープを抜けたので参照カウントがデクリメントされる。

_retn()

現在持っているオブジェクト参照の所有権を放棄してポインタを返します。

 T_ptr _retn()
 {
    T_ptr tmp = pd_objref;
    pd_objref = T_Helper::_nil();
    return tmp;
 }

通常、関数の戻り値にオブジェクト参照を返す場合に使用されます。 所有権は関数の呼び出し側に渡るので、呼び出し側ではオブジェクト参照を破棄する必要があります。 従って、呼び出し側で release するか、_var型変数で受ける必要があります。

逆に、戻り値でオブジェクト参照を返す場合、呼び出し側で release することにより参照カウントがデクリメントされるため、関数内では _duplicate() などで所有権を複製しておく必要があります。

  • 使用例
     MyObject_ptr myfunc()
     {
        MyObject_var ret;
        ret = m_obj; // ×所有権がretに移ってしまう。
        // return 後、関数の外で release されるかもしれない。
     
        ret = MyObject::_duplicate(m_obj); // ○所有権の複製
        // return 後、release が呼ばれても、m_obj は所有権を保持し続ける。
     
        return ret._retn();
     }
     
     { // 別のコンテキスト
        MyObject_var obj;
        obj = myfunc(); // オブジェクトの所有権を取得
        obj->function();
     
        MyObject_ptr ptr;
        ptr = myfunc(); //オブジェクトの所有権を取得
        ptr->function();
     
        CORBA::release(ptr); // 参照カウントをデクリメント
      }
      // スコープを抜けたので参照カウントがデクリメントされる

規則のまとめ

関数 release責任 関数内
in T_ptr 呼出側 オペレーション呼出
out T_ptr& 呼出側 _duplicate 代入
inout T_ptr& in:関数, out:呼出側 release後, _duplicate代入
_retn T_ptr 呼出側 _duplicateしてreturn

_var型、_ptr型の代入でのリファレンスカウント

_ptr型への_var型の代入

ポインタへの代入。

複製なし、解放せず。

  • 使用例

 { 
    MyObject_var var;
    var = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_ptr ptr
    ptr = MyObject::_duplicate(var); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    // ptr = var;
    // これは参照カウントエラーを引き起こす恐れがある。呼び出し後、ptrとvarは同じオブジェクト
    // をさすであろうが、参照カウントの保守はなされな。varは、その対象オブジェクトの所持を維持
    // する。また、ptrがそれが以前に指していたオブジェクトやプロキシへの唯一のポインタであった
    // とすれば、メモリリークが生じる。
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // var に関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_ptr型の代入

_var が保有しているオブジェクトに対して release() されるが、引数で渡された _ptr型のオブジェクトに対しては duplicate() されません。

複製なし、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (T_ptr p)
 {
    T_Helper::release(pd_objref);
    pd_objref = p;
    return *this;
  }

  • 使用例

 { 
    MyObject_ptr ptr;
    ptr = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var obj;
    obj = MyObject::_duplicate(ptr); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // objに関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_var型の代入

_var が保有しているオブジェクトに対して release() がコールされ、 かつ、引数で渡されたオブジェクトに対しても duplicate() がコールされる。

複製あり、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (const T_var& p)
 {
    if( &p != this )
    {
        T_Helper::duplicate(p.pd_objref);
        T_Helper::release(pd_objref);
        pd_objref = p.pd_objref;
     }
    return *this;
  }

  • 使用例

 { 
    MyObject_var var1;
    var1 = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var var2;
    var2 = var1; //オブジェクトの所有権を取得(参照カウントは自動でインクリメントされる)
 
    } // var1, var2に関しては、スコープを抜けたので参照カウントがデクリメントされる

_narrow()でのリファレンスカウント

_narrow()処理の過程において、_narrow()の呼び出しが成功した場合、その対象オブジェクトのリファレンスカウントはインクリメントされますが、失敗した場合はインクリメントされません。

デクリメントは行われない。

  • 実装(RTCSK.cc)

 RTC::RTObject_ptr
 RTC::RTObject::_narrow(::CORBA::Object_ptr obj)
 {
    if( !obj || obj->_NP_is_nil() || obj->_NP_is_pseudo() ) return _nil();
    _ptr_type e = (_ptr_type) obj->_PR_getobj()->_realNarrow(_PD_repoId);
    return e ? e : _nil();
 }

  • 実装(omniObjRef.cc)

 void*
 omniObjRef::_realNarrow(const char* repoId)
 {
    // Attempt to narrow the reference using static type info.
    void* target = _ptrToObjRef(repoId);
 
    if( target )
    {
        if (!lid ||
            (lid && !lid->deactivated() && lid->servant() &&
             lid->servant()->_ptrToInterface(repoId)))
        {
               omni::duplicateObjRef(this);
            }
           else
        {
                  omniObjRef* objref;
                  omniIOR*    ior;
            ior = pd_ior->duplicateNoLock();
              }
 
        objref = omni::createObjRef(repoId,ior,1,0);
    }
    else
    {
            if( _real_is_a(repoId) )
        {
            omniObjRef* objref;
            omniIOR* ior;
            {
                ior = pd_ior->duplicateNoLock();
            }
 
            {
                objref = omni::createObjRef(repoId,ior,1,_identity());
            }
         }
      }
    return target;
 }

規則

クライアント側

クライアントが呼び出しからオブジェクト参照を受信するならば、そのクライアントはそのオブジェクト参照が不要となったときにはそれを開放しなくてはならない。

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

サーバー側

呼び出し側に渡す参照の所有権は放棄される(つまり、その参照カウントは一つデクリメントされる。したがって、通常は、参照を返す前に適当な_duplicate()関数を呼び出すことになる)

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

参考文献

  • 『CORBA分散オブジェクト Orbixを用いて』 著: ショーン・ベーカー 出版社: ピアソン・エデュケーション

サービスコンシューマからサービスプロバイダの状態を取得する

コンシューマからプロバイダを呼び出す際には、サービスポートが接続されていて、かつ相手の RTC が Active 状態になっている必要があります。コンシューマは、コンシューマが接続されているか、相手の RTC がアクティブかどうかを以下の方法で確認することができます。

CORBA コンシューマ型 (C++ では RTC::CorbaComsumer<T>) は以下の3つの状態を取ることができます。

  • nil: コンシューマにプロバイダのオブジェクト参照がセットされていない(接続されていない)状態
  • active: プロバイダのオブジェクト参照は割り当てられており、かつ相手のオブジェクトが活性化 (RTC も Active 状態) になっている状態
  • inactive: プロバイダのオブジェクト参照は割り当てられているが、相手のオブジェクトが非活性化 (RTC は Inactive 状態) になっている状態
プロバイダの状態と RTC の状態は連動しています。

RTC::CorbaComsumer<T> が nil かどうかは、CORBA の標準関数:CORBA::is_nil() で確認することができます。 さらに、オブジェクトがアクティブかどうかは、CORBA オブジェクトのメンバ関数、_non_existent() で確認することができます。

こういった情報は CORBA のマニュアルやドキュメントから得ることができます。一番確実なのは OMG から入手できる CORBA の仕様書を読むことです。

しかしながら、これらの標準仕様書はページ数も多いので、例えば VisiBroker などの CORBA 製品のマニュアルを利用するとよいかもしれません。

以上の情報から以下のようなサンプルコードが書けます。

 RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id)
 {
   try
     {
       if (CORBA::is_nil(m_myservice0._ptr()))
         {
           std::cout << "[nil] object reference is not assigned." << std::endl;
         }
       else
         {
           if (m_myservice0->_non_existent())
             {
               std::cout << "[inactive] provider is inactive." << std::endl;
             }
           else
             {
               std::cout << "[active] provider is active." << std::endl;
             }
         }
     }
   catch (...)
     {
       std::cout << "Unknown exception." << std::endl;
     }
   coil::sleep(1);
   return RTC::RTC_OK;
 }

このコードの onExecute 関数を OpenRTM-aist のサンプル SimpleService の MyServiceConsumer.cpp の oExecute 関数と入れ替えて MyServiceProviderComp とともに実行してみてください。 MyServiceConsumerComp をアクティブ化すると、MyServiceProviderComp のサービスポートが接続されるまではコンシューマ (m_myserivce0) の状態は nil です。MyServiceProviderComp のポートを接続すると、inactive 状態になり、その後 MyServiceProvider をアクティブ化すると active 状態になります。

以上のようにして、相手の RTC の状態をサービスポートを通して知ることもできます。

独自IDLファイルの利用

RTC Builderでプロジェクト作成

RTCBuilder でプロジェクトの作成を行います。

「RTC Builder Project」のアイコンをクリックし、表示したウィンドウにプロジェクト名を入力し「終了」すると、左のウィンドウに生成したプロジェクトファイルが表示します。

sp_idl_0.png
「RTC Builder Project」ウィンドウを表示

sp_idl_00.png
idlフォルダーの確認

そこに独自データ型を配置するidlフォルダーがあるので、idlファイルをドラッグ&ドロップや[右クリック]>[貼り付け]などで置いてください。

ここではサンプルコンポーネントのSampleServiceコンポーネントが使用するIDLファイル(MyService.idl)を使います。 IDLファイル(MyService.idl)の保存先は以下です。
  • Windows環境でパッケージをインストールした場合は[インストールディレクトリ\OpenRTM-aist\1.2.2\Components\Python\Examples\SimpleService\MyService.idl]です。

プロジェクトフォルダーは、RTCBuilder起動直後に表示する「ディレクトリをワークスペースとして選択」をデフォルトで進めた場合、[ c:\Users\ユーザ名\workspase ]フォルダー以下にあります。

独自IDLの作成

RTCBuilder で独自idlを使ったコンポーネントの作成を行います。

サービスポート設定タブを開き、[Add Port]を選択しサービスポート(ServicePort)を定義します。

[ Add Interface ] ボタンを2回クリックし[ Service Interface ] を表示・選択すると[ Service Interface Profile ]を表示します。

sp_idl_01.png
独自インターフェースを使う

[Reload] をクリックするとidlフォルダー内のidlファイルが読み込まれ、 *インターフェース型 のプルダウンで新たに定義した独自インターフェースを選択できます。

その他、コンポーネント作成に必要な項目の設定が終わったら、基本タブに戻り、[コード生成] ボタンをクリックし、コードの生成を行います。

コンフィギュレーション (応用編)

コンフィギュレーション(基本編)では、コンフィギュレーションの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。

コールバックの利用

コンフィギュレーションパラメータのコールバックの利用について説明します。
コンフィギュレーションには、以下のコールバックがあります。

  • OnUpdateCallback
  • OnUpdateParamCallback
  • OnSetConfigurationSetCallback
  • OnAddConfigurationAddCallback
  • OnRemoveConfigurationSetCallback
  • OnActivateSetCallback

以下のようにしてコールバックを設定します。

OnUpdateCallback

 class MyOnUpdate
     : public RTC::OnUpdateCallback
 {
 public:
     MyOnUpdate(ConfigurationTest *obj)
     {
         myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateCallback\t" << config_set << std::endl;
    }
 private:
     ConfigurationTest *myobj;
 };

OnUpdateParamCallback

 class MyOnUpdateParam
    : public RTC::OnUpdateParamCallback
 {
 public:
    MyOnUpdateParam(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set, const char* config_param)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateParamCallback\t" << config_set << "\t" << config_param <<  std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnSetConfigurationSetCallback

 class MyOnSetConfigurationSet
    : public RTC::OnSetConfigurationSetCallback
 {
 public:
    MyOnSetConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnSetConfiguration\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnAddConfigurationAddCallback

 class MyOnAddConfigurationAdd
    : public RTC::OnAddConfigurationAddCallback
 {
 public:
    MyOnAddConfigurationAdd(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnAddConfigurationAdd\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnRemoveConfigurationSetCallback

 class MyOnRemoveConfigurationSet
    : public RTC::OnRemoveConfigurationSetCallback
 {
 public:
    MyOnRemoveConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnRemoveConfigurationSet\t" << config_set << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnActivateSetCallback

 class MyOnActivateSet
    : public RTC::OnActivateSetCallback
 {
 public:
    MyOnActivateSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_id)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnActivateSet\t" << config_id << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

サンプル

参考までに、コールバックが呼ばれると実行周期がコンフィギュレーションパラメーター Interval に設定されるという例を示します。 アクティブ、非アクティブに遷移したときにコールバックにより、コンフィギュレーションパラメーターが変更されることを確認します。

コンフィギュレーション(初期編) を参照して、初期パラメーターを設定します。

 static const char* configurationtest_spec[] =
  {
    "implementation_id", "ConfigurationTest",
    "type_name",            "ConfigurationTest",
    "description",            "Configuration Test Component",
    "version",                  "1.0.0",
    "vendor",                  "hogehoge",
    "category",               "TEST",
    "activity_type",         "PERIODIC",
    "kind",                     "DataFlowComponent",
    "max_instance",       "1",
    "language",              "C++",
    "lang_type",             "compile",
    // Configuration variables
    "conf.default.Interval",   "1000",
    "conf.default.Test",        "0",
    // Widget
    "conf.__widget__.Interval",   "text",
    "conf.__widget__.Test",        "text",
    // Constraints
    "conf.__constraints__.Interval",    "0 < x < 10000",
    ""
  };

 RTC::ReturnCode_t ConfigurationTest::onInitialize()
 {
   this->m_configsets.setOnUpdate(new MyOnUpdate(this));
   this->m_configsets.setOnUpdateParam(new MyOnUpdateParam(this));
   this->m_configsets.setOnSetConfigurationSet(new MyOnSetConfigurationSet(this));
   this->m_configsets.setOnRemoveConfigurationSet(new MyOnRemoveConfigurationSet(this));
   this->m_configsets.setOnAddConfigurationSet(new MyOnAddConfigurationAdd(this));
   this->m_configsets.setOnActivateSet(new MyOnActivateSet(this));
 
   bindParameter("Interval", m_interval, "1000");
   bindParameter("Test", m_test, "0");
  
   return RTC::RTC_OK;
 }

m_configsets は ConfigAdminクラス (コンフィギュレーション情報管理オブジェクト)で、ConfigAdmin.h にコールバックの定義があります。

まず、RTC を起動して、RTC を配置します。コンソールには以下のように表示されます。

ConfigurationCallback01.png

ConfigurationCallback01-1.png


次に、RTC をアクティブにします。コンソールには以下のように表示されます。
ConfigurationCallback05.png

ConfigurationCallback02-1.png

 RTC::ReturnCode_t ConfigurationTest::onActivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "1");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Test");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
    return RTC::RTC_OK;
 }


Interval を「1000」から「1」に設定します。

 cproperties.setProperty("Interval", "1");


コンフィギュレーションセットを取得し追加します。このときに OnSetConfiguration が呼ばれます。

 this->m_configsets.setConfigurationSetValues(cproperties);


コンフィギュレーションセットをアクティブ化します。このときに OnSetActivateSet が呼ばれますが、まだコンフィギュレーションパラメーターは変更されていません。

 this->m_configsets.activateConfigurationSet("default");


もう一つのコンフィギュレーションパラメーター Test のみをアップデートします。Interval は「1000」のままです。

 this->m_configsets.update("default","Test");


コンフィギュレーションセットを更新し Interval に「1」を設定します。

 this->m_configsets.update("default");



次に、アクティブ化したまま RTSystemEditor で Interval を「2」に変更します。コンソールには以下のように表示されます。

ConfigurationCallback03.png ConfigurationCallback04.png


ConfigurationCallback03-1.png

まず、上述同様、コンフィギュレーションセットへの更新パラメータの追加とアクティブ化が行われます。 onExecute の後 または onStateUpdate() の直後に更新が行われます。



続いて、RTC を非アクティブにします。コンソールには以下のように表示されます。

ConfigurationCallback06.png

ConfigurationCallback06-1.png

 RTC::ReturnCode_t ConfigurationTest::onDeactivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "800");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Interval");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
   return RTC::RTC_OK;
 }

アクティブにしたときと異なるのは、Interval を 800 にした箇所と、m_configsets.update("default","Interval") として Interval のみ更新しているところです。 今回は、m_configsets.update("default","Interval") で値が更新されているか確認できます。



続いて、RTSyetemEditor でコンフィギュレーションセットを追加します。 図で [追加] ボタンをクリックした後に、[適用] ボタンをクリックすると OnAddConfigurationAddCallback を呼ぶことができます。 コンソールには以下のように表示されます。

ConfigurationCallback07.png

ConfigurationCallback07-1.png

同様に、コンフィギュレーションセットを削除することで OnRemoveConfigurationSetCallback を呼ぶことができます。


基本的に、パラメーターの変更は RTC のアクティビティが呼び出されるまではコンフィギュレーションパラメーターが外部で変更されても反映されませんが、コールバックを使うことでいろいろな設定ができるようになります。

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()