3.1 Hello World using XML and Dynamic Data
The files for this example are in the directory <path to examples>/connext_dds/c++/hello_world_xml_dynamic. See Chapter 2 Paths Mentioned in Documentation.
This simple scenario consists of two applications, illustrated in the figure below: HelloWorld_publisher.exe, which writes the Topic, HelloWorldTopic, and HelloWorld_subscriber.exe, which subscribes to that Topic.
Figure 3.1: Hello World Domain
First we will build and run the application, then we will examine the configuration file and source code.
3.1.1 Build the Application
The example code is provided in C++, C#, and Java. The following instructions describe how to build it on Windows, Linux, and macOS systems. If you will be using an embedded platform, see the RTI Connext Core Libraries Getting Started Guide Addendum for Embedded Systems for instructions specific to these platforms.
- In Windows Explorer, go to <path to examples>\connext_dds\c++\hello_world_xml_dynamic\win32 and open the Microsoft® Visual Studio® solution file for your architecture. For example, the file for Visual Studio 2017 32-bit platforms is Hello-i86Win32VS2017.sln.
Note: If your Windows SDK Version is not 10.0.15063.0, you may be prompted to retarget the file. If this happens, in the Retarget Projects window that appears, select an installed version of Windows SDK and click OK.
- The Solution Configuration combo box in the toolbar indicates whether you are building debug or release executables; select Release. Then select Build Solution from the Build menu.
- From your command shell, change directory to <path to examples>/connext_dds/c++/ hello_world_xml_dynamic.
- Type:
make -f make/makefile_Hello_<architecture>
where <architecture> is one of the supported architectures (e.g., makefile_Hello_x64Linux4gcc8.5.0); see the contents of the make directory for a list of available architectures. This command will build a release executable. To build a debug version instead, type:
make -f make/makefile.<architecture> DEBUG=1
3.1.2 Run the Application
The previous step should have built one executable: Hello.exe. This application should be in the proper architecture subdirectory under the objs directory (for example, objs\x64Win64VS2017 in the Windows example cited below and objs/x64Linux4gcc8.5.0 in the Linux example).
From your command shell, go to <path to examples>\connext_dds\c++\ hello_world_xml_dynamic and type:
objs\<architecture>\Hello pub
where <architecture> is the architecture you just built; look in the objs directory to see the name of the architecture you built. For example, the Windows architecture name corresponding to 32-bit Visual Studio 2017 is i86Win32VS2017.
From your command shell, change directory to <path to examples>/connext_dds/c++/ hello_world_xml_dynamic and type:
objs/<architecture>/Hello pub
where <architecture> is the architecture you just built; look in the objs directory to see the name of the architecture you built. For example, x64Linux4gcc8.5.0.
From a different command shell, go to <path to examples>\connext_dds\c++\ hello_world_xml_dynamic and type:
objs\<architecture>\Hello sub
where <architecture> is the architecture you just built; look in the objs directory to see the name of the architecture you built. For example, the Windows architecture name corresponding to 32-bit Visual Studio 2017 is i86Win32VS2017.
From a different command shell, change directory to <path to examples>/connext_dds/c++/ hello_world_xml_dynamic and type:
objs/<architecture>/Hello sub
where <architecture> is the architecture you just built; look in the objs directory to see the name of the architecture you built. For example, x64Linux4gcc8.5.0.
You should immediately see some messages from the publishing application showing that it is writing data and messages from the subscribing application showing the data it receives. Do not worry about the contents of the messages. They are generated automatically for this example. The important thing is to understand how the application is defined, which will be explained in the following sections.
3.1.3 Examine the XML Configuration Files Definition
A Connext application is defined in the file USER_QOS_PROFILES.xml found in the directory <path to examples>/connext_dds/c++/ hello_world_xml_dynamic. Let’s review its content to see how this scenario was constructed. The main sections in the file are:
- 3.1.3.1 QoS Definition
- 3.1.3.2 Type Definition
- 3.1.3.3 Domain Definition
- 3.1.3.4 Participant Definition
The entire file is shown below. We will examine the file section-by-section.
<?xml version="1.0"?>
<dds version="7.7.0"
xsi:noNamespaceSchemaLocation=
"http://community.rti.com/schema/current/rti_dds_profiles.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- Qos Library -->
<qos_library name="qosLibrary">
<qos_profile name="DefaultProfile">
</qos_profile>
</qos_library>
<!-- types -->
<types>
<const name="MAX_NAME_LEN" value="64" type="int32"/>
<const name="MAX_MSG_LEN" value="128" type="int32"/>
<struct name="HelloWorld">
<member name="sender" type="string"
stringMaxLength="MAX_NAME_LEN" key="true"/>
<member name="message" type="string"
stringMaxLength="MAX_MSG_LEN"/>
<member name="count" type="int32"/>
</struct>
</types>
<!-- Domain Library -->
<domain_library name="MyDomainLibrary">
<domain name="HelloWorldDomain" domain_id="0">
<register_type name="HelloWorldType"
type_ref="HelloWorld"/>
<topic name="HelloWorldTopic"
register_type_ref="HelloWorldType">
<topic_qos name="HelloWorld_qos"
base_name="qosLibrary::DefaultProfile"/>
</topic>
</domain>
</domain_library>
<!-- Participant library -->
<domain_participant_library name="MyParticipantLibrary">
<domain_participant name="PublicationParticipant"
domain_ref="MyDomainLibrary::HelloWorldDomain">
<publisher name="MyPublisher">
<data_writer name="HelloWorldWriter"
topic_ref="HelloWorldTopic"/>
</publisher>
</domain_participant>
<domain_participant name="SubscriptionParticipant"
domain_ref="MyDomainLibrary::HelloWorldDomain">
<subscriber name="MySubscriber">
<data_reader name="HelloWorldReader"
topic_ref="HelloWorldTopic">
<datareader_qos base_name="qosLibrary::DefaultProfile"/>
</data_reader>
</subscriber>
</domain_participant>
</domain_participant_library>
</dds>
3.1.3.1 QoS Definition
The defined DDS Entities have an associated QoS. The QoS section of the XML file provides a way to define QoS libraries and profiles, which can then be used to configure the QoS of the defined Entities.
The syntax of the QoS libraries and profiles section is described in Configuring QoS with XML, in the RTI Connext Core Libraries User's Manual and may also contain Entity configurations.
In this example, the QoS library and profile are empty, just to provide a placeholder where the QoS can be specified. Using this empty profile results in the default DDS QoS being used:
<!-- QoS Library -->
<qos_library name="qosLibrary">
<qos_profile name="DefaultProfile">
</qos_profile>
</qos_library>
3.1.3.2 Type Definition
The data associated with the HelloWorld Topic consists of two strings and a numeric counter:
- The first string contains the name of the sender of the message. This field is marked as “key” as signals the identity of the data-object.
- The second string contains a message.
- The third field is a simple counter which the application increments with each message.
This example uses the Dynamic Data API, so the data type must be defined in the XML configuration. You can do this by adding the type definition within the <types> tag:
<types>
<const name="MAX_NAME_LEN" type="int32" value="64"/>
<const name="MAX_MSG_LEN" type="int32" value="128"/>
<struct name="HelloWorld">
<member name="sender" type="string" key="true" stringMaxLength="MAX_NAME_LEN"/>
<member name="message" type="string" stringMaxLength="MAX_MSG_LEN"/>
<member name="count" type="int32"/>
</struct>
</types>
The <types> tag may be used to define a library containing the types that the different applications will need. You can even refer to other files containing types by using the XML include tag within the <types> tag. (For example: <include file="PrimitiveTypes.xml"/>.) For this simple example, just one data-type, the HelloWorld type seen above, is included.
3.1.3.3 Domain Definition
The domain section is used to define the system’s Topics and the corresponding data types associated with each Topic. To define a Topic, the associated data type must be registered with the domain, giving it a registered type name. The registered type name is used to refer to that data type within the domain at the time the Topic is defined.
In this example, the configuration file registers the previously defined HelloWorld type under the name HelloWorldType. Then it defines a Topic named HelloWorldTopic, which is associated with the registered type, referring to it by its registered name, HelloWorldType:
<!-- Domain Library -->
<domain_library name="MyDomainLibrary" domain_id=”0” >
<domain name="HelloWorldDomain">
<register_type name="HelloWorldType" type_ref="HelloWorld"/>
<topic name="HelloWorldTopic" register_type_ref="HelloWorldType"/>
</domain>
</domain_library>
Notes:
- The attribute type_ref in the <register_type> element refers to the same HelloWorld type defined in the <types> section.
- A domain definition may register as many data types and define as many Topics as it needs. In this example, a single data type and Topic will suffice.
- The domain_library can be used to define multiple domains. However, this example only uses one domain.
3.1.3.4 Participant Definition
The participant section is used to define the DomainParticipants in the system and the DataWriters and DataReaders that each participant has. DomainParticipants are defined within the <domain_participant_library> tag.
Each DomainParticipant:
- Has a unique name (within the library) which will be used later by the application that creates it.
- Is associated with a domain, which defines the domain_id, Topics, and data types the DomainParticipant will use.
- Defines the Publishers and Subscribers within the DomainParticipant. Publishers contain DataWriters, Subscribers contain DataReaders.
- Defines the set of DataReaders it will use to write data. Each DataReader has a QoS and a unique name which can be used from application code to retrieve it.
- Defines the set of DataWriters it will use to write data. Each DataWriter has a QoS and a unique name which can be used from application code to retrieve it.
- Optionally the Participants, Publishers, Subscribers, DataWriters, and DataReaders can specify a QoS profile that will be used to configure them.
The example below defines two DomainParticipants, called PublicationParticipant and SubscriptionParticipant:
<domain_participant_library name="MyParticipantLibrary">
<domain_participant name="PublicationParticipant" domain_ref="MyDomainLibrary::HelloWorldDomain"> <publisher name="MyPublisher"> <data_writer name="HelloWorldWriter" topic_ref="HelloWorldTopic"/> </publisher> </domain_participant> <domain_participant name="SubscriptionParticipant" domain_ref="MyDomainLibrary::HelloWorldDomain"> <subscriber name="MySubscriber"> <data_reader name="HelloWorldReader" topic_ref="HelloWorldTopic"> <datareader_qos base_name="qosLibrary::DefaultProfile"/> </data_reader> </subscriber>
</domain_participant>
</domain_participant_library>
Examining the XML, we see that:
- PublicationParticipant is bound to the domain, MyDomainLibrary::HelloWorldDomain.
- The participant contains a single Publisher named MyPublisher, which itself contains a single DataWriter named HelloWorldWriter.
- The DataWriter writes the Topic HelloWorldTopic, which is defined in the domain MyDomainLibrary::HelloWorldDomain.
Similarly:
- SubscriptionParticipant is also bound to the domain MyDomainLibrary::HelloWorldDomain.
- The participant contains a single Subscriber named MySubscriber, which itself contains a single DataReader named HelloWorldReader.
- The DataReader reads the Topic HelloWorldTopic, which is defined in the domain MyDomainLibrary::HelloWorldDomain.
Since both participants are in the same domain and the HelloWorldWriter DataWriter writes the same Topic that the HelloWorldReader DataReader reads, the two participants will communicate as depicted in Figure 3.1: Hello World Domain .
3.1.4 Publisher Application
Open the file <path to examples>/connext_dds/c++/hello_world_xml_dynamic/src/HelloWorld_publisher.cxx and look at the source code.
The logic of this simple application is contained in the publisher_main() function. The logic is composed of two parts:
- Entity Creation
The application first creates a DomainParticipant using the function create_participant_from_config(). This function takes the configuration name of the participant, MyParticipantLibrary::PublicationParticipant, which is the same name that was specified in the XML file. Note that the name in the XML file, PublicationParticipant, has been qualified with the name of the library it belongs to: MyParticipantLibrary.
DDSDomainParticipant * participant = DDSTheParticipantFactory->create_participant_from_config( "MyParticipantLibrary::PublicationParticipant");
This single function call registers all the necessary data types and creates and the Topics and Entities that were specified in the XML file. In this simple case, the participant only contains a Publisher, MyPublisher, with a single DataWriter, HelloDataWriter. However, in more realistic scenarios, this single call can create hundreds of entities (both readers and writers).
The remaining part of the function uses the created Entities to perform the logic of the program.
This example writes data using the single DataWriter. So the application looks up the HelloWorldWriter DataWriter using the fully qualified name MyPublisher::HelloWorldWriter and narrows it to be a DynamicDataWriter:
DDSDynamicDataWriter * dynamicWriter = DDSDynamicDataWriter::narrow(participant->lookup_datawriter_by_name( "MyPublisher::HelloWorldWriter"));
Once the DataWriter is available, some data objects need to be created and used to send the data. As this example uses dynamic data, and the type code is internally created, you can use the operations create_data() and delete_data() in a DataWriter to create and delete a data object. This is achieved with the calls seen below:
/* Create data */
DDS_DynamicData *dynamicData = dynamicWriter->create_data(DDS_DYNAMIC_DATA_PROPERTY_DEFAULT);
/* Main loop to repeatedly send data */
for (count=0; count < 100 ; ++count) {
/* Set the data fields */
retcode = dynamicData->set_string( "sender", DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, "John Smith");
retcode = dynamicData->set_string( "message", DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, "Hello World!");
retcode = dynamicData->set_long( "count", DDS_DYNAMIC_DATA_MEMBER_ID_UNSPECIFIED, count);
/* Write the data */
retcode = dynamicWriter->write(*dynamicData, DDS_HANDLE_NIL);
...
}
/* Delete data sample */
dynamicWriter->delete_data(dynamicData
Note that operations such as set_long() are used to set the different attributes of the Dynamic Data object. These operations refer to the attribute names (e.g., “count”) that were defined as part of the data type.
3.1.5 Subscriber Application
Open the file <path to examples>/connext_dds/c++/hello_world_xml_dynamic/src/HelloWorld_subscriber.cxx and look at the source code.
The logic of this simple application is contained in the subscriber_main() function. Similar to the publisher application, the logic is composed of two parts:
- Entity Creation
The application first creates a DomainParticipant using the function create_participant_from_config(). This function takes the configuration name of the participant MyParticipantLibrary::SubscriptionParticipant, which is the same name that was specified in the XML file. Notice that the name in the XML file, SubscriptionParticipant, has been qualified with the name of the library it belongs to: MyParticipantLibrary.
DDSDomainParticipant * participant =
DDSTheParticipantFactory->create_participant_from_config( "MyParticipantLibrary::SubscriptionParticipant”);
This single function call registers all the necessary data types and creates and the Topics and Entities that were specified in the XML file. In this simple case, the participant only contains a Subscriber, MySubscriber, with a single DataReader, HelloDataReader. However in more realistic scenarios, this single call can create hundreds of Entities (both DataReaders and DataWriters).
The remaining part of the function uses the entities that were created to perform the logic of the program.
This example only needs to read data using the single DataReader. So the application looks up the HelloWorldReader DataReader using the fully qualified name MySubscriber::HelloWorldReader and narrows it to be a DynamicDataReader:
DDSDynamicDataReader * dynamicReader = DDSDynamicDataReader::narrow(
participant-> lookup_datareader_by_name(
"MySubscriber::HelloWorldReader"));
To process the data, the application installs a Listener on the DataReader. The HelloWorldListener, defined on the same file implements the DataReaderListener interface, which the DataReader uses to notify the application of relevant events, such as the reception of data.
/* Create a DataReaderListener */
HelloWorldListener * reader_listener = new HelloWorldListener();
/* set listener */
retcode = dynamicReader->set_listener(reader_listener, DDS_DATA_AVAILABLE_STATUS);
The last part is the implementation of the listener functions. In this case, we only implement the on_data_available() operation which is the one called when data is received.
The on_data_available() function receives all the data into a sequence and then uses the DDS_DynamicData::print() function to print each data item received.
void HelloWorldListener::on_data_available(DDSDataReader* reader)
{
DDSDynamicDataReader * ddDataReader = NULL;
DDS_DynamicDataSeq dataSeq;
DDS_SampleInfoSeq infoSeq;
DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
DDS_Long i = 0;
ddDataReader = DDSDynamicDataReader::narrow(reader);
retcode = ddDataReader->take(dataSeq, infoSeq, DDS_LENGTH_UNLIMITED, DDS_ANY_SAMPLE_STATE, DDS_ANY_VIEW_STATE, DDS_ANY_INSTANCE_STATE);
printf("on_data_available:%s\n",
ddDataReader->get_topicdescription()->get_name());
for (i = 0; i < dataSeq.length(); ++i) {
if (infoSeq[i].valid_data) {
retcode = dataSeq[i].print(stdout, 0);
}
}
retcode = ddDataReader->return_loan(dataSeq, infoSeq);
}
3.1.6 Subscribing with a Content Filter
To use a content filter, modify the SubscriptionParticipant configuration to look like this:
<domain_participant_library name="MyParticipantLibrary">
...
<domain_participant name="SubscriptionParticipant"
domain_ref="MyDomainLibrary::HelloWorldDomain">
<subscriber name="MySubscriber">
<data_reader name="HelloWorldReader" topic_ref="HelloWorldTopic">
<datareader_qos name="HelloWorld_reader_qos"
base_name="qosLibrary::DefaultProfile"/>
<content_filter name="HelloWorldTopic" kind="builtin.sql">
<expression>count > 2</expression>
</content_filter>
</data_reader>
</subscriber>
</domain_participant>
</domain_participant_library>
The extra XML within the <content_filter> tag adds a SQL content filter which only accepts samples with the field count greater than two.
Now run HelloWorld_subscriber without recompiling and confirm that you see the expected behavior.