Writing (n-ando)
(GA) In the Data Port (Basic), the basic usage of the data port was explained. In the advanced section, I will explain how to use it a little more.
In addition to the predefined data types (eg TimedLong, TimedDouble, etc.), you can also use your own defined data types in the data port. However, before creating a new data type yourself, make sure that a similar data type is not already defined, and define a new data type only if the required data type is not found in it. Is recommended.
In OpenRTM-aist, the data type used for the data port is defined in the following IDL file.
Create a folder for the component. Here, we will create a folder called “UserDefType” directly under the C drive. (C: \UserDefType)
Create an IDL file that defines the data type. The data type is defined by the struct keyword. The following basic types, string types, and sequence types are available.
Type | Meaning | Declaration Example |
short | short integer | short shortVariable; |
long | long integer | long longVariable; |
unsinged short | short integer | unsigned short ushortVariable; |
unsigned long | long integer | unsigned long ulongVariable; |
float | Single precision floating point | float floatVariable; |
double | double precision floating point number | double doubleVariable; |
char | Character type | char charVariable; |
wchar | wchar character type | char charVariable; |
boolean | bool type | bool shortVariable; |
octet | octet type | octet octetVariable; |
longlong | longlong integer | longlong longlongVariable; |
ulonglong | unsinged longlong integer | ulonglong ulonglongVariable; |
sequence <T> | Sequence type | sequence <double> doubleSeqVariable; |
In this example, MyDataType.idl defines a data type called MyData.
// @file MyDataType.idl
#include "BasicDataType.idl" struct MyData { RTC::Time tm; short shortVariable; long longVariable; sequence<double> data; };
On the second line #include "BasicDataType.idl" Is necessary to use the first field tm of MyData type (RTC::Time type). Unless you have a specific reason, declare the first field as RTC::Time tm; to store the time stamp, even for custom data types.
Create a project with RTCBuilder.
Click the "RTC Builder Project" icon, enter the project name in the displayed window, and click "Finish" to display the generated project file in the left window.
Since there is an idl folder where you can place your own data type, please place the IDL file (MyDataType.idl) of your own data type by dragging and dropping or [Right click]> [Paste].
The project folder is located under the [c: \ Users \ username \ workspase] folder if you proceed by default with "Select directory as workspace" to be displayed immediately after starting RTC Builder.
Create a component using idl of the original data type in RTCBuilder.
Open the Data Port Settings tab and define the data port (InPort / OutPort).
If you want to use your own data type in the created data port, click [Reload] to load MyDataType.idl in the idl folder, and you can select the newly defined MyData from the * data type pull-down.
After setting the other items required for component creation, return to the basic tab and click the [Code Generation] button to generate the code.
We have already mentioned that InPort checks for data arrival with isNew() and reads it with read(), or OutPort sends out data with write().
For example, InPort cannot obtain data until it is called by read function after calling isNew() in a function such as onExecute(). Even if the onExecute() cycle is very fast, the timing at which data arrives at InPort and the timing at which the actual processing is performed are asynchronous.
What if you want to process data as soon as it arrives, that is, synchronously? As a way to achieve this, OpenRTM-aist defines callbacks that are called at various data port and connector processing timings.
There are four types of callbacks: 1) InPort, 2) OutPort, 3) connector, and 4) port.
InPort provides two types of callbacks: These are defined in rtm / PortCallback.h.
LEFT: 55 | LEFT: 150 | c |
OnRead | Set by InPort :: setOnRead () function that is called when InPort read () is called. | |
OnReadConvert | Called to convert data when InPort read () is called. Set by InPort :: setOnReadConvert () function. |
The OnRead callback is a callback used to return data with some conversion to the caller when read() is called and OnReadConvert is called when read() is called.
Each callback is implemented by inheriting the base class of each functor defined in rtm/PortCallback.h.
A functor makes an object callable in the same syntax as a normal function. In C ++, it can be realized by overloading operator (). In C language, a callback is realized by giving a function pointer to the caller, but it is difficult to give a state variable to the callback itself by using only the function pointer. Since functors are themselves objects, they can have state variables, and can be called like C functions.
Each implementation example is shown below.
#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() function was called." << std::endl; std::cout << "read() function was called." << 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; } };
In MyOnRead functor that inherits OnRead, output stream std :: ostream is passed in the constructor. It is intended to pass a file output stream std :: ofstream etc. opened somewhere. The functor entity operator()() outputs a character string to the output stream and standard output. In this way, functors can also call other objects by passing state variables in advance in the constructor.
On the other hand, MyOnReadConvert which inherits OnReadConvert<T> implements only operator()(const T&). The argument of this function is passed the data before being read into the InPort variable when read() is called. Some processing is performed in this function, and the data returned by return is written to the InPort variable. In this example, the square is calculated and returned assuming that the data type has a member called data and the multiplication operator is defined. If you use a variable type that does not have an appropriate member, you will get a compilation error.
Now, let's actually incorporate this functor into the component. As a sample using InPort, ConsoleOut, which is a sample included in OpenRTM-aist, is used here. When ConsoleOut expands the OpenRTM-aist source,
OpenRTM-aist-<version>/examples/SimpleIO/
If you install from a package etc.
/usr/share/OpenRTM-aist/examples/src/
Below is the source code.
First, write the above class definition in ConsoleOut.h. It is better to describe the class definition in a separate source, but the functor class is only used in this component and its content is short. In such cases, define it including the implementation in the header. It does n’t matter.
// ConsoleOut.h Omission // Service Consumer stub headers // <rtc-template block="consumer_stub_h"> // </rtc-template> using namespace RTC; // Add from here template <class T> class MyOnRead : public RTC::OnRead<T> { public: MyOnRead(std::ostream& os) : m_os(os) {}; virtual void operator()() { m_os << "read() function was called." << std::endl; std::cout << "read() function was called" << 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; } }; // up to here class ConsoleOut : public RTC::DataFlowComponentBase { Omission protected: // DataInPort declaration // <rtc-template block="inport_declare"> TimedLong m_in; InPort<TimedLong> m_inIn; Omission private: //Added from here MyOnRead<TimedLong>* m_onread; MyOnReadConvert<TimedLong>* m_onreadconv; //Added upto here };
First, declare the callback functors MyOnRead and MyOnReadConvert before declaring the ConsoleOut class. In order to have pointer variables of these classes as members, declare each pointer variable in the private part. At this time, please note that both MyOnRead / MyOnReadConvert give TimedLong, which is the same as the InPort type of this component, to the type argument of the class template.
OutPort provides the following two types of callbacks. These are defined in rtm / PortCallback.h.
LEFT: 40 | LEFT: 150 | c |
OnWrite | Set by OutPort :: setOnWrite () function that is called when OutPort's write () is called. | |
OnWriteConvert | Called to convert data when OutPort write () is called. Set by OutPort :: setOnWriteConvert () function. |
The OnWrite callback is a callback used to send data with some sort of conversion when write () is called, and OnWriteConvert is called when write () is called.
Each callback is implemented by inheriting the base class of each functor defined in rtm / PortCallback.h like InPort.
Each implementation example is shown below.
#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() was called. " << std::endl; std::cout << "write() was called. " << 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; } };
The way to write a callback functor is almost the same as InPort OnRead / OnReadConvert. MyOnWrite functor that inherits OnWrite passes the output stream std :: ostream in the constructor. It is intended to pass a file output stream std :: ofstream etc. opened somewhere. The functor entity operator () outputs a character string to the output stream and standard output. In this way, functors can also call other objects by passing state variables in advance in the constructor.
On the other hand, MyOnReadConvert which inherits OnReadConvert <T> implements only operator () (constT &). The argument of this function is passed the data before being read into the InPort variable when read () is called. Some processing is performed in this function, and the data returned by return is written to the InPort variable. In this example, the square is calculated and returned assuming that the data type has a member called data and the multiplication operator is defined. If you use a variable type that does not have an appropriate member, you will get a compilation error.
A connector is an object that abstracts buffers and communication paths. As shown in the figure, it exists between OutPort and InPort, data is written from OutPort by write () function, and data is read from InPort by read () function. The connector abstracts and hides how the data is transmitted from OutPort to InPort.
OutPort is a buffer in the connectorOutPort can be connected to multiple InPorts, but one connector is created for each connection. (In fact, InPort can have multiple connections at the same time, but since there is no way to distinguish the data, it is not normally used.) In other words, if there are three connections, there are three connectors, each with a write status.
You can also see that for these functions, there must be one connector for each OutPort / InPort pair. Furthermore, when modeling the connector at the implementation level corresponding to the subscription type, we introduced an object for asynchronous communication called publisher.
A data port creates one connector object per connection when a connection is established. A connector is an abstract channel of data stream that connects OutPort and InPort.
ON_BUFFER_WRITE | When writing to the buffer |
ON_BUFFER_FULL | When buffer is full |
ON_BUFFER_WRITE_TIMEOUT | Buffer write timeout |
ON_BUFFER_OVERWRITE | When buffer is overwritten |
ON_BUFFER_READ | When reading buffer |
ON_SEND | When sending to InProt |
ON_RECEIVED | When sending to InProt is complete |
ON_RECEIVER_FULL | InProt side buffer full |
ON_RECEIVER_TIMEOUT | InProt side buffer timeout |
ON_RECEIVER_ERROR | InProt side error |
ON_BUFFER_EMPTY | If the buffer is empty |
ON_BUFFER_READTIMEOUT | If the buffer times out and is empty |
ON_SENDER_EMPTY | OutPort buffer is empty |
ON_SENDER_TIMEOUT | OutPort side timeout |
ON_SENDER_ERROR | OutPort side error |
ON_CONNECT | When establishing a connection |
ON_DISCONNECT | When disconnected |
The data port returns a status when data is sent and received. The status is defined in rtm/DataPortStatus.h.
LEFT: 80 | LEFT: 150 | c |
PORT_OK | Successful completion | |
PORT_ERROR | Abnormal termination | |
BUFFER_ERROR | Buffer error | |
BUFFER_FULL | ||
BUFFER_EMPTY | Buffer Empty | |
BUFFER_TIMEOUT | Buffer timeout | |
SEND_FULL | Sending data but buffer is full | |
SEND_TIMEOUT | Sending data but the other side timed out | |
RECV_EMPTY | Data sent but data is empty | |
RECV_TIMEOUT | Trying to receive data but timed out | |
INVALID_ARGS | Invalid argument | |
PRECONDITION_NOT_MET | Precondition not met | |
CONNECTION_LOST | Connection lost | |
UNKNOWN_ERROR | Unknown error |
An example of creating a custom data port interface is shown below.
The following functions and classes must be defined.
Provider class of data port interface (Push type). When the put function is called on the consumer side, it is necessary to transfer data to the provider side in some way. In this sample, data is written to the file when the put function is called on the consumer side, and data is transferred by reading data from the file on the provider side.
//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(); } //function called when provider is created //Receive information on connector profile and port properties 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; } //Function executed by another thread //Read data from file periodically and write to buffer //This function is necessary for this sample, but to create your own interface //not required 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; } //Function called when connector is connected //This function is called before the consumer side subscribeInterface function //For this reason, the information set with the publishInterface function //can get //If something goes wrong, return false and disconnect the connector bool InPortTestProvider:: publishInterface(SDOPackage::NVList& properties) { //Store the name information of the file which data is written to CORBA_SeqUtil:: push_back(properties, NVUtil::newNV("dataport.test.filename", m_filename.c_str())); return true; } }; extern "C" { //This function must be called when the module is loaded void InPortTestProviderInit(void) { RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance()); factory.addFactory("test", ::coil::Creator< ::RTC::InPortProvider, ::RTC::InPortTestProvider>, ::coil::Destructor< ::RTC::InPortProvider, ::RTC::InPortTestProvider>); } };
Consumer class of data port interface (Push type). Must inherit from InPortProvider. Inheritance of the Task class is not required because it is unique to this sample.
//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
Consumer class of data port interface (Push type). In case of Push type, a consumer is created on the InPort side and a provider is created on the OutPort side. When the put function is called on the consumer side, it is necessary to transfer data to the provider side in some way. In this sample, data is written to the file when the put function is called on the consumer side, and data is transferred by reading data from the file on the provider side.
//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>); } };
Consumer class of data port interface (Push type). Must inherit from InPortConsumer.
//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
The OpenRTM-aist manager calls the “XXXInit” function when the dynamic link library “XXX.dll” is loaded. For this sample, load "InPortTestInterface.dll" and call the following "InPortTestInterfaceInit" function.
//InPortTestInterface.cpp #include "InPortTestConsumer.h" #include "InPortTestProvider.h" extern "C" { DLL_EXPORT void InPortTestInterfaceInit(RTC::Manager* manager) { InPortTestProviderInit(); InPortTestConsumerInit(); } };
1. Prepare an environment where OpenRTM-aist is installed.
2. Create a CMake configuration file (CMakeLists.txt) for building.
The following is a CMake configuration file for building a custom interface sample.
//Description to find OpenRTM-aist library 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 name setting project (InPortTestInterface) //Create a dynamic library add_library(InPortTestInterface SHARED InPortTestProvider.cpp InPortTestConsumer.cpp InPortTestProvider.h InPortTestConsumer.h InPortTestInterface.cpp) //Set library to link target_link_libraries(InPortTestInterface ${OPENRTM_LIBRARIES})
3. Generate a Visual Studio project file with CMake.
4. Build with Visual Studio.
After building, InPortTestInterface.dll is generated in the Debug folder.
5. Create rtc.conf # br Create rtc.conf and specify InPortTestInterface.dll in the module that loads when Manager starts.
manager.modules.preload: InPortTestInterface.dll
If InPortTestInterface.dll is different from the directory where RTC is executed, set the module search path separately.
manager.modules.load_path: C:¥workspace¥InPortTestInterface¥build¥Debug
ConsoleInComp.exe -f ../rtc.conf
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 { };
上記の一覧に型が該当しない場合、その型は固定長になります。
_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 される
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も所有権を持つ
単純にオブジェクト参照の_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()); // 所有権は移行されない } // スコープを抜けたので参照カウントがデクリメントされる
現在所有している参照を 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(); } // スコープを抜けたので参照カウントがデクリメントされる。
リファレンスへのポインタの参照を返します。
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 の指すものは入れ替わっているかもしれない。 } // スコープを抜けたので参照カウントがデクリメントされる。
現在持っているオブジェクト参照の所有権を放棄してポインタを返します。
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 |
ポインタへの代入。
複製なし、解放せず。
{ MyObject_var var; var = myfunc(); // オブジェクトの所有権を取得 MyObject_ptr ptr ptr = MyObject::_duplicate(var); //オブジェクトの所有権を取得(参照カウントのインクリメント) // ptr = var; // これは参照カウントエラーを引き起こす恐れがある。呼び出し後、ptrとvarは同じオブジェクト // をさすであろうが、参照カウントの保守はなされな。varは、その対象オブジェクトの所持を維持 // する。また、ptrがそれが以前に指していたオブジェクトやプロキシへの唯一のポインタであった // とすれば、メモリリークが生じる。 CORBA::release(ptr); // 参照カウントをデクリメント } // var に関しては、スコープを抜けたので参照カウントがデクリメントされる
_var が保有しているオブジェクトに対して release() されるが、引数で渡された _ptr型のオブジェクトに対しては duplicate() されません。
複製なし、解放あり。
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 が保有しているオブジェクトに対して release() がコールされ、 かつ、引数で渡されたオブジェクトに対しても duplicate() がコールされる。
複製あり、解放あり。
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()の呼び出しが成功した場合、その対象オブジェクトのリファレンスカウントはインクリメントされますが、失敗した場合はインクリメントされません。
デクリメントは行われない。
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(); }
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 オブジェクト参照のためのメモリ管理)
コンシューマからプロバイダを呼び出す際には、サービスポートが接続されていて、かつ相手の RTC が Active 状態になっている必要があります。コンシューマは、コンシューマが接続されているか、相手の RTC がアクティブかどうかを以下の方法で確認することができます。
CORBA コンシューマ型 (C++ では RTC::CorbaComsumer<T>) は以下の3つの状態を取ることができます。
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 の状態をサービスポートを通して知ることもできます。
コンフィギュレーション(基本編)では、コンフィギュレーションの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。
コンフィギュレーションパラメータのコールバックの利用について説明します。
コンフィギュレーションには、以下のコールバックがあります。
以下のようにしてコールバックを設定します。
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; };
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; };
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; };
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; };
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; };
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 を配置します。コンソールには以下のように表示されます。
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」に変更します。コンソールには以下のように表示されます。
続いて、RTC を非アクティブにします。コンソールには以下のように表示されます。
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 を呼ぶことができます。 コンソールには以下のように表示されます。
同様に、コンフィギュレーションセットを削除することで OnRemoveConfigurationSetCallback を呼ぶことができます。
基本的に、パラメーターの変更は RTC のアクティビティが呼び出されるまではコンフィギュレーションパラメーターが外部で変更されても反映されませんが、コールバックを使うことでいろいろな設定ができるようになります。
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 自身、すなわちコアロジックを包含するコンポーネントの機能の詳細にアクセスする(コンポー ネントの機能から外部のサービスにアクセスする)インターフェースを提供します。
SDO サービスの具体的な使われ方は以下のようなものです。
例えば、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 側でのプロバイダ、コンシューマの実装方法について説明します。
In this section, I will explain how to implement 'Consumer" of a SDO service.
SDOサービスコンシューマは、ツールなど外部に存在するサービスインターフェースをコールすることで機能するようなサービスを実現する手段です。
前述の ComponentObserver のように、RTC側から何かを通知したり、RTC側で外部のサービスを利用したりする場合にSDOサービスコンシューマを実装します。
まずはRTCのためのSDOコンシューマを実装する前に、外部にSDOサービスを実装する必要があります。現在は、CORBAのサーバとして、SDOPackage.idlのSDOServiceインターフェースを継承し、通常のCORBAサービスとして実装することになります。
この実装方法は、通常のCORBAサービスの実装の方法となりますので、ここでは割愛します。
前述のとおり、オブジェクトは通常、共有オブジェクト (so, DLL) としてコンパイル・リンクされます。このオブジェクトが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サービスコンシューマを実装する際には、SdoServiceConsumerBase 基底クラスを継承した一つのクラスを作成します。
#include <rtm/SdoServiceConsumerBase.h> class MySdoServiceConsumer : SdoServiceConsumerBase {
このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。
以下に、各関数の詳細な振る舞いを示す。
関数プロトタイプ bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)
初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。外部からSDOサービスが ServiceProfile とともにアタッチされると、SDOコンシューマがインスタンス化され、その直後に SDO サービスがアタッチされた RTC と与えられた ServiceProfile を引数としてこの関数が呼ばれる。
関数内では、ServiceProfile 内の SDO サービスリファレンスを CorbaConsumer クラス等を利用しオブジェクト内に保持するとともに、properties から設定内容を読み込みサービス固有の設定等を行う。与えられたサービスのオブジェクトリファレンスが不正、あるいは properties の内容が不正、等の場合は戻り値に false を返す。
関数プロトタイプ bool reinit(const SDOPackage::ServiceProfile& profile)
再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。
関数プロトタイプ const SDOPackage::ServiceProfile& getProfile() const
設定されたプロファイルを返す関数です。
関数プロトタイプ const SDOPackage::ServiceProfile& getProfile() const
終了処理。コンシューマがデタッチされる際に呼び出される関数です。関数内では終了処理を実装します。
関数プロトタイプ 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のオプションで別途指定する必要があります。)
以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。
この節では SDO サービスのプロバイダの実装方法について説明します。
SDO サービスプロバイダは、自らサービスを外部のツールやアプリケーション・RTC などに対して提供する主体となります。
前述のとおり、SDO サービスプロバイダのオブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされます。 このオブジェクトが RTC のプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。
[RTC] [SDO service] [Other] | : | | instantiate : | |------------->: | | init() | | |------------->| | | | get_service_profiles() | |<--------------------------------------| | | get_sdo_service() | |<--------------------------------------| | | use service | | |<-----------------------| | | | | finalize() | | |------------->x | x x |
SDO サービスプロバイダを実装する際には、SdoServiceProviderBase 基底クラスおよび、CORBA サーバントスケルトンクラスを継承した一つのクラスを作成します。
#include <rtm/SdoServiceProviderBase.h> class MySdoServiceConsumer : SdoServiceProviderBase, {
このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。
以下に、各関数の詳細な振る舞いを示す。
関数プロトタイプ bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)
初期化関数。与えられた 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 内に保持されます。
関数プロトタイプ bool reinit(const SDOPackage::ServiceProfile& profile)
再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。
関数プロトタイプ const SDOPackage::ServiceProfile& getProfile() const
設定されたプロファイルを返す関数です。
関数プロトタイプ const SDOPackage::ServiceProfile& getProfile() const
終了処理。RTCオブジェクトが解体されるか、init()処理においてfalseが返された場合は、この関数が呼び出されたのちオブジェクトは解体されます。関数内では終了処理を実装します。
関数プロトタイプ 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のオプションで別途指定する必要があります。)
以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。