OpenRTM-aist-1.0.0-RELEASEにおけるRC1からの変更点

主な変更概要

  • IDL に様々なデータ型の定義が追加された。
  • RTObject_impl に、複数の OutPort の write をまとめて呼び出す機能、複数の InPort の read をまとめて呼び出す機能が追加された。
  • ポートのコールバックの設定方法が変更された。
  • データポートの接続時の振舞いを実行時に変更できるプロパティが追加された。
  • Manager にマスター/スレーブという概念が導入された。

coil

  • 以下の機能が追加
    • Process: プロセスを起動する
    • Routing: ネットワークカードが複数ある場合、適切なアドレスを取得する

IDL

  • BasicDataType: TimedWchar, TimedWstring が追加。
  • ExtendedDataTypes: Point, Vector, Size など汎用的な型が定義されている。
  • InterfaceDataType: Actuator, RangeData, Pathなど、ロボットで利用しそうな型が定義されている。

RTObject_impl

ポートの登録に関するメンバ関数の追加

 void registerPort(CorbaPort& port);
 bool addPort(PortBase& port);
 bool addPort(PortService_ptr port);
 bool addPort(CorbaPort& port);
 bool addInPort(const char* name, InPortBase& inport);
 bool addOutPort(const char* name, OutPortBase& outport);

ポートの登録: 既存の registerPort などのメンバ関数との違いは、登録の成否を

    戻り値で返すか否かである。登録が失敗するのは既に同じポート名や
    同じポートのインスタンスが登録されていた場合である。
    それ以外の機能は同じである。

 bool removePort(PortBase& port);
 bool removePort(PortService_ptr port);
 bool removeInPort(InPortBase& port);
 bool removeOutPort(OutPortBase& port);
 bool removePort(PortBase& port);
 bool removePort(PortService_ptr port);
 bool removePort(CorbaPort& port);

ポートの登録解除: 既存の deletePort などのメンバ関数との違いは、登録の成否を戻り値で返すか否かである。

登録が失敗するのは既に同じポート名や同じポートのインスタンスが登録されていた場合である。 それ以外の機能は同じである。

データポートの読み書きに関するメンバ関数の追加

 bool readAll();

  • addInPort/registerInPortで登録されたInPortに対してまとめてreadを行う

   class DataIn: public RTC::DataFlowComponentBase
   {
     RTC::TimedLong input_data1_;
     RTC::InPort<RTC::TimedLong> inport1_;
     RTC::TimedLong input_data2_;
     RTC::InPort<RTC::TimedLong> inport2_;
   public:
     explicit DataIn(RTC::Manager* manager):
       RTC::DataFlowComponentBase(manager),
       input_data1_(),
       inport1_("inport1", input_data1_),
       input_data2_(),
       inport2_("inport2", input_data2_)
     {
     }
 
     virtual RTC::ReturnCode_t onInitialize()
     {
       registerInPort("inport1", inport1_);
       registerInPort("inport2", inport2_);
       return RTC::RTC_OK;
     }
 
     virtual RTC::ReturnCode_t onExecute(RTC::UniqueId exec_handle)
     {
       // inport1_とinport2_に対してreadを呼び出す
       const bool is_read_success = readAll();
       if (is_read_success)
       {
         std::cout << input_data1_.data << std::endl;
         std::cout << input_data2_.data << std::endl;
       }
       return RTC::RTC_OK;
     }
   };
 
 bool writeAll();

  • addOutPort/registerOutPortで登録されたOutPortに対してまとめてwriteを行う
   class DataOut: public RTC::DataFlowComponentBase
   {
     RTC::TimedLong output_data1_;
     RTC::OutPort<RTC::TimedLong> outport1_;
     RTC::TimedLong output_data2_;
     RTC::OutPort<RTC::TimedLong> outport2_;
   public:
     explicit DataOut(RTC::Manager* manager):
       RTC::DataFlowComponentBase(manager),
       output_data1_(),
       outport1_("outport1", output_data1_),
       output_data2_(),
       outport2_("outport2", output_data2_)
     {
     }
 
     virtual RTC::ReturnCode_t onInitialize()
     {
       registerOutPort("outport1", outport1_);
       registerOutPort("outport2", outport2_);
       return RTC::RTC_OK;
     }
 
     virtual RTC::ReturnCode_t onExecute(RTC::UniqueId exec_handle)
     {
       output_data1_.data += 2;
       output_data2_.data += 3;
 
       // outport1_とoutport2_に対してwriteを呼び出す
       const bool is_write_success = writeAll();
       if (!is_write_success)
       {
         std::cout << "write fail" << std::endl;
       }
       return RTC::RTC_OK;
     }
   };
 
 
 void setReadAll(bool read=true, bool completion=false);
  • onExecuteが呼び出される直前に自動でreadAll()を呼び出すように設定する

   class DataIn: public RTC::DataFlowComponentBase
   {
     RTC::TimedLong input_data1_;
     RTC::InPort<RTC::TimedLong> inport1_;
     RTC::TimedLong input_data2_;
     RTC::InPort<RTC::TimedLong> inport2_;
   public:
     explicit DataIn(RTC::Manager* manager):
       RTC::DataFlowComponentBase(manager),
       input_data1_(),
       inport1_("inport1", input_data1_),
       input_data2_(),
       inport2_("inport2", input_data2_)
     {
     }
 
     virtual RTC::ReturnCode_t onInitialize()
     {
       registerInPort("inport1", inport1_);
       registerInPort("inport2", inport2_);
       setReadAll(); // ★
       return RTC::RTC_OK;
     }
 
     virtual RTC::ReturnCode_t onExecute(RTC::UniqueId exec_handle)
     {
       // onExecuteが呼び出される直前にinport1_とinport2_のreadが呼ばれている
       std::cout << input_data1_.data << std::endl;
       std::cout << input_data2_.data << std::endl;
       return RTC::RTC_OK;
     }
   };

 void setWriteAll(bool write=true, bool completion=false);
  • onExecuteが呼び出された直後に自動でwriteAll()を呼び出すように設定する

   class DataOut: public RTC::DataFlowComponentBase
   {
     RTC::TimedLong output_data1_;
     RTC::OutPort<RTC::TimedLong> outport1_;
     RTC::TimedLong output_data2_;
     RTC::OutPort<RTC::TimedLong> outport2_;
   public:
     explicit DataOut(RTC::Manager* manager):
       RTC::DataFlowComponentBase(manager),
       output_data1_(),
       outport1_("outport1", output_data1_),
       output_data2_(),
       outport2_("outport2", output_data2_)
     {
     }
 
     virtual RTC::ReturnCode_t onInitialize()
     {
       registerOutPort("outport1", outport1_);
       registerOutPort("outport2", outport2_);
       setWriteAll(); // ★
       return RTC::RTC_OK;
     }
 
     virtual RTC::ReturnCode_t onExecute(RTC::UniqueId exec_handle)
     {
       output_data1_.data += 2;
       output_data2_.data += 3;
       return RTC::RTC_OK;
       // onExecuteを抜けた直後にoutport1_とoutport2_のwriteが呼ばれる
     }
   };

その他処理タイミング等の変更

  • RTC初期化時に有効にするコンフィギュレーションセットを指定するRTCのプロパティのキー名が変更された。
    • (RC1) "active_config"
    • (RELEASE) "configuration.active_config"
  • initialize内でon_initializeが呼び出されるタイミングが変更された。
    • (RC1) ExecutionContextを生成する前
    • (RELEASE) ExecutionContextを生成した後
  • on_initialize内でonInitializeが呼び出されるタイミングが変更された。
    • (RC1) RTCのプロパティ"active_config"に指定されているコンフィギュレーションセットを有効にした後。
    • (RELEASE) RTCのプロパティ"configuration.active_config"に指定されているコンフィギュレーションセットを有効にする前。
  • RTObject_impl::getObjRef()がオブジェクトリファレンスをduplicateせずに返すようになった。 これにより、呼び出し側は本メソッドの戻り値をRTObject_varで受け取ってはいけなくなった。
  • finalizeを外部から呼び出せなくなった。呼び出すと常にPRECONDITION_NOT_METでエラー復帰する。finalizeはexitを呼び出すことで間接的に呼び出される。

ポート全般

コールバック関連

  • 以下のコールバック用クラスが削除された。
    • OnOverflow
    • OnUnderflow
    • OnWriteTimeout
    • OnReadTimeout
      • 代わりにConnectorListenerを使用する
    • OnConnect
    • OnDisconnect
      • 代わりにConnectionCallbackを使用する
  • データを伴うコールバック(ConnectorDataListenerType) とデータを伴わないコールバック(ConnectorListenerType) が用意されている。
    • ConnectorDataListenerType
      • 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側エラー時
    • ConnectorListenerType
      • ON_BUFFER_EMPTY: バッファが空の場合
      • ON_BUFFER_READTIMEOUT: バッファが空でタイムアウトした場合
      • ON_SENDER_EMPTY: OutPort側バッファが空
      • ON_SENDER_TIMEOUT: OutPort側タイムアウト時
      • ON_SENDER_ERROR: OutPort側エラー時
      • ON_CONNECT: 接続確立時
      • ON_DISCONNECT: 接続切断時
  • ConnectorDataListenerType のコールバックを利用するためには、ConnectorDataListenerTを継承したリスナクラスを用意する。
       class MyDataListener
         : public ConnectorDataListenerT<RTC::TimedLong>
       {
       public:
         virtual void operator()(const ConnectorInfo& info,
                                 const TimedLong& data)
         {
           // コールバックの処理
         };
       };
  • ポートに対して addConnectorDataListener でリスナを登録する。
       MyDataListener *listner = new MyDataListener();
       m_port.addConnectorDataListener(ON_BUFFER_WRITE, listner);
     
  • removeConnectorDataListener でリスナを解除する。
       m_port.removeConnectorDataListener(ON_BUFFER_WRITE, listner);
  • ConnectorListenerTypeのコールバックの場合も同様に、ConnectorListenerを継承したリスナクラスを用意する。
       class MyConnectorListener
         : public ConnectorListener
       {
       public:
         virtual void operator()(const ConnectorInfo& info)
         {
           // コールバックの処理
         };
       };
  • ポートに対して addConnectorListener でリスナを登録する。
       MyConnectorListener *listner = new MyConnectorListener();
       m_port.addConnectorListener(ON_BUFFER_EMPTY, listner);
  • removeConnectorListener でリスナを解除する。
       m_port.removeConnectorListener(ON_BUFFER_EMPTY, listner);
  • PortBaseに以下のpublicメンバ関数が追加された
     void setOnPublishInterfaces(ConnectionCallback* on_publish);
    • publishInterfacesの実行"後"に呼び出されるコールバックを設定する。publishInterfacesが失敗していてもコールバックは呼び出されることに注意。
 void setOnSubscribeInterfaces(ConnectionCallback* on_subscribe);
    • subscribeInterfacesの実行"前"に呼び出されるコールバックを設定する。
 void setOnConnected(ConnectionCallback* on_connected);
    • ポート間接続が成功した場合に呼び出されるコールバックを設定する。
          ミューテックスがロックされているため、コールバック内で、
          コールバックを設定したポートに対してget_connector_profilesや
          get_port_profileなどを呼び出すとデッドロックすることに注意。
          コールバックを設定したポート以外のポートに対しては
          ミューテックスがロックされていないため、get_port_profileなどの
          呼び出しは可能。
          例えば、ポート1とポート2を接続することを考える。
          ポート1にsetOnConnectedでコールバックを設定しておく。
          このコールバック内からポート1に対してget_port_profileなどの
          呼び出しを行うとデッドロックする。ポート2に対しては
          get_port_profileなどを呼び出せる。

 void setOnUnsubscribeInterfaces(ConnectionCallback* on_subscribe);

  • unsubscribeInterfacesの実行"前"に呼び出されるコールバックを設定する。
        ミューテックスがロックされているため、コールバック内で、
        接続対象ポートのget_connector_profilesやget_port_profileなどを
        呼び出すとデッドロックすることに注意。
        setOnConnectedと違い、コールバックを設定していないポートに
        対してもget_port_profileなどを呼び出せないことに注意。
        例えば、ポート1とポート2を接続することを考える。
        ポート1にsetOnConnectedでコールバックを設定しておく。
        このコールバック内からはポート1、ポート2ともに
        get_port_profileなどの呼び出しを行うとデッドロックする。
 void setOnDisconnected(ConnectionCallback* on_disconnected);
  • ポート間の切断処理の最後に呼び出されるコールバックを設定する。
        切断処理の途中に何らかのエラーがあってもコールバックは
        呼び出されることに注意。
        また、ミューテックスのロックされているため、
        setOnUnsubscribeInterfacesと同様、デッドロックに注意。
 void setOnConnectionLost(ConnectionCallback* on_connection_lost);
  • ポート接続が突然切断された場合に呼び出されるコールバックを設定する。
        ただし、このコールバックに関しては、PortBaseでは処理の実装を
        しておらず、派生クラス側で実装するようになっている。
        現在は、OutPortのみに実装されており、InPortとCorbaPortでは
        コールバックを設定しても呼び出されることはない。

その他

  • PortProfileに格納されているポート名が[RTCのインスタンス名].[ポート名]に変更された。
     ただし、addPortやregisterPortなどでポートをRTCに登録していない状態では、.[ポート名]になっている。
     また、addPortやregisterPortなどの呼び出しをonInitialize以前(コンストラクタなど)で行うと、
     RTCのインスタンス名が確定していないため、ポート名は .[ポート名]になってしまう。
  • notify_connect内でエラーが発生した場合の処理方法が変更された。
    • (RC1) 接続対象のポート全てに対するpublishInterfacesやconnectNext、
             subscribeInterfacesの呼び出しについて、途中で1つでも失敗した場合、
             即座にエラー復帰する
    • (RELEASE) 接続対象のポート全てに対するpublishInterfacesやconnectNext、
                 subscribeInterfacesの呼び出しについて、途中で失敗しても、
                 そのまま処理は続行する。接続対象ポート全てに対して処理を
                 行った後、発生したエラーに対応するエラー戻り値を返す。
  • PortBaseに以下のpublicメンバ関数が追加された
     const char* getName() const;

・PortBaseのprotectedメンバ関数eraseConnectorProfileが

 ミューテックスのロックを行わなくなった。

・PortBaseの以下のメンバ関数を呼び出した際、

 接続しているポートを全てチェックし、無効になったポートが
 あれば、切断処理を行うようになった。
   get_port_profile()
   get_connector_profiles()
   get_connector_profile()

・ポートのプロパティ"connection_limit"を指定すると、

 接続数の制限ができるようになった。

■データポート 【コールバック関連】 ・InPortの以下のコールバック設定関数が削除された。

 その代わり、addConnectorDataListenerを使用する。
 void setOnWrite(OnWrite<DataType>* on_write);
 void setOnWriteConvert(OnWriteConvert<DataType>* on_wconvert);
 void setOnOverflow(OnOverflow<DataType>* on_overflow);
 void setOnUnderflow(OnUnderflow<DataType>* on_underflow);

・OutPotの以下のメンバ関数が削除された。

 コールバックの設定はaddConnectorDataListenerを使用する。
 bool read(DataType& value);
 void setReadBlock(bool block);
 void setWriteBlock(bool block);
 void setReadTimeout(long int timeout);
 void setWriteTimeout(long int timeout);
 void setOnOverflow(OnOverflow<DataType>* on_overflow);
 void setOnRead(OnRead<DataType>* on_read);
 void setOnReadConvert(OnReadConvert<DataType>* on_rconvert);
 void setOnUnderflow(OnUnderflow<DataType>* on_underflow);
 void setOnConnect(OnConnect* on_connect);
 void setOnDisconnect(OnConnect* on_disconnect);
 void onConnect(const char* id, PublisherBase* publisher);
 void onDisconnect(const char* id);

【接続時のポリシ設定】 ・RT System Editorでデータポートを接続する際、

 送信ポリシとバッファリングポリシを指定できるようになった。

 送信ポリシ(接続方式がflush(デフォルト)の場合は使えない)
  - All: InPortのバッファにたまっているデータを一度にすべて送信する
  - Fifo: InPortのバッファにたまっているデータをFIFOとみなして(古い方から1つず
つ)送信する
  - Skip: InPortのバッファにたまっているデータを間引いて送信する
  - New: InPortのバッファにたまっているデータの最新値のみを送信する

 バッファリングポリシ
 ・バッファ書き込み時にバッファがフルだった場合の動作を指定できる。
  - overwrite: 上書きする
  - do_nothing: 何もしない
  - block: ブロックする。タイムアウト設定可。

 ・バッファ読み込み時にバッファが空だった場合の動作を指定できる。
  - readback: 最新の値を読み直す
  - do_nothing: 何もしない
  - block: ブロックする。タイムアウト設定可。

・データポートの接続時のプロパティに"dataport.serializer.cdr.endian"

 を指定すると、送受信するデータのエンディアンを変更できる。
 しかし、RT System Editorは対応していない。

【その他】 ・DataPortStatusにBUFFER_ERRORが追加された。

・データポートでCORBAの代わりにTCP/IPを用いて通信を行うことが

 できなくなった。
 (TCP/IP用のProvider/Consumerを自作すれば可能だと思われる)

・InPort::readの戻り値の型が変更された。

 (RC1) 読み込んだデータ
 (RELEASE) 読み込みの成否を表すbool

・Timed*のデータに対して現在時刻を付与する関数setTimestamp()が追加された。

・OutPort::write()で、タイムスタンプを付与しなくなった。

・OutPotに以下のメンバ関数が追加された。

 これを用いると、1対多接続の際に接続ごとのwriteの結果が取得できる。
 DataPortStatus::Enum getStatus(int index);
 DataPortStatusList getStatusList();

・データポートのpullの動作

 - InPort::isNew()は常にfalseを返す。
 - 送信側のバッファにデータがない場合は、InPort::read()の戻り値がfalseになる。
 - OutPort側のBuffer empty policyの設定により、バッファが空だった場合の動作が異なる。
  デフォルトではreadbackになっているため、一度データをwrite()すると同じ値を読み続
ける。

  データポートをpushで接続した場合と、pullで接続した場合ではプログラミングスタイ
ルが異なる。

 [pushの場合]
   RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id) {
     if (m_inport.isNew()) {
       m_inport.read();
       // データを利用した処理
     }
     return RTC::RTC_OK;
   }

 [pullの場合]
   RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id) {
     bool ret = m_inport.read();
     if (ret == true) {
       // データを利用した処理
     }
     return RTC::RTC_OK;
   }

■サービスポート 【接続時のポリシ設定】 ・CorbaPortの接続において、ConnectorProfile::propertiesに

 "port.connection.strictness"というプロパティを設定できるようになった。
 このプロパティには"strict"か"best_effort"を設定する。
 CorbaPort.hより引用
  * strict: すべてのコンシューマに指定した参照が存在し、かつナローイン
  *         グにも成功しコンシューマに適切にセットできた場合にのみ Port
  *         間の接続を確立する。
  *
  * best_effort: ナローイング等に失敗した場合でも、エラーを返すことな
  *         く Port 間の接続を確立する。

【その他】 ・CorbaPortで、接続中にRTCをActivate、Deactivateし、再度Activateすると

 コンシューマ側からアクセスできない問題が解消された。

■Manager関係 Managerにマスターとスレーブという関係ができた。 詳細は[openrtm-users 01099]の安藤様の解説を参照のこと。

【rtc.confに与えるプロパティの変更】 ・Managerのプロパティ(rtc.confに与えるプロパティ)に変更があった。

 "corba.endpoint"
   使用できなくなった。代わりに"corba.endpoints"を使用することになった。

 "manager.is_master"
   NOにするとManagerのCORBAオブジェクトをネーミングサービスに
   登録しないようになった。

 "manager.corba_servant"
   NOにするとManagerのCORBAオブジェクトを生成しない。

 "manager.is_master"
   YESであれば、Managerのプロパティ"corba.master_manager"で
   指定されているポート番号をORBのエンドポイントとして
   追加するようになった。

 "manager.shutdown_on_nortcs"
   YESの場合、"manager.is_master"NOであれば、プロセス内のRTCが0個に
   なったタイミングでManagerが自動終了するようになった。
   ただし、[openrtm-users 01149]のパッチをあてること。

【RTC起動時のコマンドライン引数のオプションの追加】

 -a
   指定すると、ManagerのCORBAオブジェクトを生成しない。
   ただし、ManagerConfig::parseArgs()にバグがあるため、
   このオプションを指定してもManagerのCORBAオブジェクトの
   生成が有効になる。("maanger.corba_servant"でスペルミス)
   SVNの最新ソースでは修正されている模様。
 -d
   指定すると、Managerがマスターになる。
 -o
   Managerのプロパティを追加できる。
   このオプションに続けて (プロパティのキー):(プロパティの値)と書く。
   例えば、-o test.property:100
 -p
   Managerのプロパティ"corba.endpoints"にポート番号を追加する。
   例えば、-p 50000

【Managerの内部実装の変更】 ・RC1では未実装だったModuleManager::getLoadableModules()が実装された。

 → Manager::getLoadableModules()が使えるようになった。

・Manager::setModuleInitProc()で設定した関数が実行される

 タイミングが変更された。
 Manager::activateManager()で実行されること自体は変更されていない。
 しかし、Managerのプロパティ"manager.components.precreate"で
 指定されたRTCを起動する処理との順番が変更されている。
 (RC1) "manager.components.precreate"のRTCを生成した後
 (RELEASE) "manager.components.precreate"のRTCを生成する前

・ManagerにRTCのガベージコレクションのような機能が追加された。

 RTCをexitすると、Managerが1秒周期でexitされたRTCをdeleteする。
 ただし、バグあり。[openrtm-users 01149]のパッチをあてること。
 また、createComponentで生成したRTCがon_initializeで失敗すると
 そのRTCはdeleteされず、メモリリークする。
 RTCのinitializeが失敗するとRTCをexitするが、
 exitはinitializeが成功していないと処理を拒否するため、
 Manager::notifyFinalizedが呼ばれず、Managerの
 RTCガベージコレクションが働かないため。

・ManagerがRTCを削除する際、deleteを直接使うのではなく、

 Manager::registerFactoryで登録されたRTC削除関数を使うように
 修正された。

・Manager::load()で、第2引数の初期化関数名が空の場合の処理が追加された。

 初期化関数名が空の場合は、第1引数のモジュール名に"Init"を付与した
 初期化関数名でモジュールのロードを試みるようになった。

・Manager::createComponentで、指定したRTCを生成するFactoryが

 登録されていない場合、Managerのプロパティ"manager.modules.load_path"で
 指定したパスから、指定したRTCを生成可能なモジュールを検索し、
 自動ロードする機能が追加された。

【IDLインタフェースの変更】 ・IDLインタフェースRTM::Managerが変更された。

 (追加)
 boolean is_master();
 ManagerList get_master_managers();
 RTC::ReturnCode_t add_master_manager(in Manager mgr);
 RTC::ReturnCode_t remove_master_manager(in Manager mgr);
 ManagerList get_slave_managers();
 RTC::ReturnCode_t add_slave_manager(in Manager mgr);
 RTC::ReturnCode_t remove_slave_manager(in Manager mgr);
 (削除)
 Manager get_owner();
 Manager set_owner(in Manager mgr);
 Manager get_child();
 Manager set_child(in Manager mgr);

・ManagerServant::get_loadable_modules()、

 ManagerServant::get_loaded_modules()、
 ManagerServant::get_factory_profiles()は、本来、
 自分のものだけでなく、スレーブのモジュールも
 取得できるようなメソッドのようだが、
 現在の実装では自分のモジュールしか取得できない。

ダウンロード

最新バージョン : 2.0.1-RELESE

統計

Webサイト統計
ユーザ数:2160
プロジェクト統計
RTコンポーネント307
RTミドルウエア35
ツール22
文書・仕様書2

Choreonoid

モーションエディタ/シミュレータ

OpenHRP3

動力学シミュレータ

OpenRTP

統合開発プラットフォーム

産総研RTC集

産総研が提供するRTC集

TORK

東京オープンソースロボティクス協会

DAQ-Middleware

ネットワーク分散環境でデータ収集用ソフトウェアを容易に構築するためのソフトウェア・フレームワーク