3.2 Hello World using XML and Compiled Types

The files for this example are in the directory <path to examples>/connext_dds/c++/hello_world_xml_compiled. This simple scenario consists of two applications identical in purpose to the one illustrated in Figure 3.1: Hello World Domain : HelloWorld_publisher.exe, which writes to the Topic “HelloWorldTopic,” and HelloWorld_subscriber.exe, which subscribes to that same Topic.

In contrast with 3.1 Hello World using XML and Dynamic Data, which uses the Dynamic Data API, this example uses compiled types.

Compiled types are syntactically nicer to use from application code and provide better performance. The drawback is that there is an extra step of code-generation involved to create that supporting infrastructure to marshal and unmarshal the types into a format suitable for network communications.

3.2.1 Define the Data Types using IDL or XML

The first step is to describe the data type in a programming language-neutral manner. Two languages are supported by the Connext tools: XML and IDL. These languages (XML and IDL) provide equivalent type-definition capabilities, so you can choose either one depending on your personal preference. You can even transform between one and the other with the RTI tools. That said, as the rest of the configuration files use XML, it is often more convenient to also use XML to describe the data types, so they can be shared or moved to other XML configuration files.

The directory <path to examples>/connext_dds/c++/hello_world_xml_compiled contains the IDL description of the data type in the file HelloWorld.idl.

Let’s examine the contents of the IDL file:

const long MAX_NAME_LEN = 64;
const long MAX_MSG_LEN  = 128;
 
struct HelloWorld {
    string<MAX_NAME_LEN> sender; //@key
    string<MAX_MSG_LEN> message;
    long count;
};

The file defines a structure type called “HelloWorld” consisting of a string (the sender), a string (the message), and an integer count.

If you want to create an XML description of the data type, that XML would look exactly like the type-declaration syntax in the USER_QOS_PROFILES.xml file used for the dynamic example (3.1.3.2 Type Definition).

3.2.2 Generate Type-Support Code from the Type Definition

This step produces code to support the direct use of the structure ‘HelloWorld’ from application code. The code is generated using the provided tool named rtiddsgen.

The Code Generator supports many programming languages. XML-Based Application Creation currently supports C, C++, Java, and C#. We will use C++ in this example.

To generate code, follow these steps (replacing <architecture> as needed for your system; e.g., x64Win64VS2017 or armv8Linux4gcc7.3.0):

On a Windows system:

From your command shell, change directory to <path to examples>\connext_dds\c++\hello_world_xml_compiled and type:

<NDDSHOME>\bin\rtiddsgen –language C++ -example <architecture> HelloWorld.idl

On a Linux or macOS system:

From your command shell, change directory to <path to examples>/connext_dds/c++/hello_world_xml_compiled and type:

<NDDSHOME>/bin/rtiddsgen –language C++ -example <architecture> HelloWorld.idl

As a result of this step you will see the following files appear in the directory hello_world_xml_compiled: HelloWorld.h, HelloWorld.cxx, HelloWorldPlugin.h, HelloWorldPlugin.cxx, HelloWorldSupport.h, and HelloWorldSupport.cxx.

The most notable thing at this point is that the HelloWorld.h file contains the declaration of the C++ structure, built according to the specification in the XML file:

static const DDS_Long MAX_NAME_LEN = 64;
static const DDS_Long MAX_MSG_LEN = 128;

typedef struct HelloWorld
{
	char*  sender; /* maximum length = ((MAX_NAME_LEN)) */
	char*  message; /* maximum length = ((MAX_MSG_LEN)) */
	DDS_Long  count;
} HelloWorld;

3.2.3 Build the Application

The example code is provided in C++, C#, and Java. The following instructions describe how to build it on Linux, macOS, and Windows 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.

C++ on Windows Systems:

In the Windows Explorer, go to <path to examples>\connext_dds\c++\hello_world_xml_compiled and open the Microsoft Visual Studio solution file for your architecture. For example, the file for Visual Studio 2017 32-bit platforms is HelloWorld-i86Win32VS2017.sln. (You have to have already generated code as described in 3.2.2 Generate Type-Support Code from the Type Definition in order to see this file.)

The Solution Configuration combo box in the toolbar indicates whether you are building debug or release executables; select Release. Select Build Solution from the Build menu.

C++ on Linux and macOS systems:

From your command shell, change directory to <path to examples>/connext_dds/c++/hello_world_xml_compiled.

Type:

make -f makefile_Hello_<architecture>

where <architecture> is one of the supported architectures (e.g., makefile.armv8Linux4gcc7.3.0). This command will build a release executable. To build a debug version instead, type:

make -f makefile_Hello_<architecture> DEBUG=1

3.2.4 Run the Application

The previous step built two executables: HelloWorld_subscriber and HelloWorld_publisher. These applications should be in proper architecture subdirectory under the objs directory (for example, objs\x64Win64VS2017 in the Windows example cited below and objs/armv8Linux4gcc7.3.0 in the Linux example).

  1. Start the subscribing application:
  2. On a Windows system:

    From your command shell, go to <path to examples>\connext_dds\c++\hello_world_xml_compiled and type:

    objs\<architecture>\HelloWorld_subscriber.exe

    where <architecture> is the architecture you just built; see the contents of 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.

    On a Linux or macOS system:

    From your command shell, change directory to <path to examples>/connext_dds/c++/hello_world_xml_compiled and type:

    objs/<architecture>/HelloWorld_subscriber

    where <architecture> is the architecture you just built of the supported architectures; examine the contents of the objs directory to see the name of the architecture you built.

  1. Start the publishing application:
  2. On a Windows system:

    From your command shell, go to <path to examples>\connext_dds\c++\hello_world_xml_compiled and type:

    objs\<architecture>\HelloWorld_publisher.exe

    where <architecture> is the architecture you just built; see the contents of the objs directory to see the name of the architecture you built.

    On a Linux or macOS system:

    From your command shell, change directory to <path to examples>/connext_dds/c++/hello_world_xml_compiled and type:

    objs/<architecture>/HelloWorld_publisher

You should immediately see some messages on the publishing application showing that it is writing data and messages in the subscribing application indicating 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 subsections.

3.2.5 Examine the XML Configuration Files Definition

This system is defined in the file USER_QOS_PROFILES.xml in the directory <path to examples>/connext_dds/c++/hello_world_xml_compiled. Let’s look at its content and what are the elements defined to construct this scenario.

<?xml version="1.0"?>
<dds version="7.5.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>

<!-- Domain Library -->
<domain_library name="MyDomainLibrary">
    <domain name="HelloWorldDomain" domain_id="0"> 
	<register_type name="HelloWorldType"/>
	<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 
				 name="HelloWorld_reader_qos"
				 base_name="qosLibrary::DefaultProfile"/>
			</data_reader>
		</subscriber>
	</domain_participant>
</domain_participant_library>
</dds>

Notice that this file contains virtually the same information found in the hello_world_xml_dynamic example. This is no surprise, since we are essentially trying to define the same system. Please see 3.1.3 Examine the XML Configuration Files Definition for a description of what each section in the XML does.

There are only two differences in the configuration file for the hello_world_xml_compiled compared to hello_world_xml_dynamic:

  • The type definition “<types>” section does not appear in the configuration of the HelloWorld_xml_compiled example.
  • The type-definition section that appears between the tags “<types>” and “</types>” is not there because in this case the data types are compiled in. So the type-definition has been moved to an external file to facilitate the code generation described in 3.2.2 Generate Type-Support Code from the Type Definition.

  • The registration of the data-type inside the domain uses the syntax:
  • <register_type name="HelloWorldType" />

    This contrasts with what was used in the HelloWorld_xml_dynamic example:

    <register_type name="HelloWorldType" type_ref="HelloWorld" />. 

    The difference between the two is easily observable from the type registration mechanism in XML-Application Creation, which is a follows:

    1. If a <register_type> tag is not present, the value of the attribute register_type_ref of a {{<topic>}] is used as registered type name of a type support that must have been already registered by the application.
    2. If a <register_type> tag is specified but its attribute type_ref is not present, this is equivalent to 1, but the registered type name is the one specified by the <register_type> tag.
    3. If a <register_type> tag is specified and the type_ref is present, XML-Application Creation will first search for a type support already registered. If no type support is found, it will automatically register the type using DynamiData and with the TypeCode defined by the XML type referenced by type_ref.

This behavior enables the possibility of defining configurations that are independent of how the types are registered, leaving that decision up to the end application. That is, the same configuration can be used for applications that generate a type or that rely on DynamicData.

3.2.6 Examine the Publisher Application

Open the file <path to examples>/connext_dds/c++/hello_world_xml_compiled/HelloWorld_publisher.cxx and look at the source code.

The logic of this simple application is contained in the publisher_main() function. The logic can be seen as composed of three parts:

  • Type registration (this step is new compared to HelloWorld_xml_dynamic)
  • The first thing the application does is register the data-types that were defined in the code-generation step. This is accomplished by calling the register_type_support() function on the DomainParticipantFactory.

    /* type registration */
    retcode = DDSTheParticipantFactory->register_type_support(
    	HelloWorldTypeSupport::register_type, "HelloWorldType");

    The function register_type_support() must be called for each code-generated data type that will be associated with the Topics published and subscribed to by the application. In this example, there is only one Topic and one data type, so only one call to this function is required.

    The function register_type_support() takes as a parameter the TypeSupport function that defines the data type in the compiled code. In this case, it is HelloWorldTypeSupport::register_type(), which is declared in HelloWorldSupport.h. However, you cannot see it directly because it is defined using macros. Instead you will find the line:

    DDS_TYPESUPPORT_CPP(HelloWorldTypeSupport, HelloWorld);

    This line defines the HelloWorldTypeSupport::register_type() function.

    In general, if you include multiple data-type definitions in a single XML (or IDL) file called MyFile.xml (or MyFile.idl), you will have multiple TypeSupport types defined within the generated file MyFileTypeSupport.h. You can identify them searching for the DDS_TYPESUPPORT_CPP() macro and you should register each of them (the ones the application uses) using the operation register_type_support() as was shown above.

  • Entity creation
  • The steps to create the entities are the same as for the HelloWorld_xml_dynamic example. The application first creates a DomainParticipant using the function create_participant_from_config(), which 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 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).

  • Use of the Entities
  • The remaining part of the function uses the entities that were created to perform the logic of the program.

    This example only needs to write 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 HelloWorldDataWriter. Note the difference with the HelloWorld_xml_dynamic example. Rather than the generic “DynamicDataWriter” used in that example, here we use a DataWriter specific to the HelloWorld data type.

    HelloWorldDataWriter * helloWorldWriter = HelloWorldDataWriter::narrow(
    	participant->lookup_datawriter_by_name(
    		"MyPublisher::HelloWorldWriter"));
    /* Create data */
    HelloWorld * helloWorldData = HelloWorldTypeSupport::create_data();
    
    /* Main loop */
    for (count=0; (sample_count == 0) || (count < sample_count); ++count)
    {
    	printf("Writing HelloWorld, count: %d\n", count);
    
    	/* Set the data fields */
    	helloWorldData->sender  = "John Smith";
    	helloWorldData->message = "Hello World!";
    	helloWorldData->count   = count;
    	retcode = helloWorldWriter->write(*helloWorldData, 
    					  DDS_HANDLE_NIL);
    	if (retcode != DDS_RETCODE_OK) {
    		printf("write error %d\n", retcode);
    		publisher_shutdown(participant);
    		return -1;
    	}
    	NDDSUtility::sleep(send_period);
    }

    Note that the data-object helloWorldData can be manipulated directly as a plain-language object. Then to set a field in the object, the application can refer to it directly. For example:

    helloWorldData->count = count;

    This “plain language object” API is both higher performance and friendlier to the programmer than the DynamicData API.

3.2.7 Examine the Subscriber Application

Open the file <path to examples>/connext_dds/c++/hello_world_xml_compiled/HelloWorld_subscriber.cxx and look at the source code.

The logic of this simple application is in the subscriber_main() function. Similar to the publisher application the logic can be seen as composed of three parts:

  1. Type registration (this step is new compared to HelloWorld_xml_dynamic)
  2. This step is identical to the one for the publisher application. The first thing the application does is register the data types that were defined in the code-generation step. This is accomplished calling the register_type_support() function on the DomainParticipantFactory.

    /* type registration */
    retcode = DDSTheParticipantFactory->register_type_support(
    	HelloWorldTypeSupport::register_type, "HelloWorldType");

    Please refer to the explanation of the publishing application for more details on this step, regardless of whether the application uses a type to publish or subscribe.

  3. Entity creation
  4. The steps for creating the entities are the same as for the HelloWorld_xml_dynamic example. 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. Note 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 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).

  5. Use of the Entities
  6. The remaining part of the function uses the created entities 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 “MyPublisher::HelloWorldReader” and narrows it to be a HelloWorldDataReader:

    HelloWorldDataReader * helloWorldReader =
    	HelloWorldDataReader::narrow(
    		participant->lookup_datareader_by_name(
    			"MySubscriber::HelloWorldReader"));

    To process the data, the application installs a Listener on the DataReader. The HelloWorldListener defined in the same file implements the DataReaderListener interface. The DataReader uses that interface to notify the application of relevant events, such as the reception of data.

    /* Create a data reader listener */
    HelloWorldListener *reader_listener = new HelloWorldListener();
    
    /* set listener */
    retcode = helloWorldReader->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 called when data is received.

    The on_data_available() function receives all the data into a sequence, then uses the HelloWorldTypeSupport::print() function to print each data item received.

    void HelloWorldListener::on_data_available(DDSDataReader* reader)
    {
    	HelloWorldDataReader *helloWorldReader = NULL;
    	HelloWorldSeq dataSeq;
    	DDS_SampleInfoSeq infoSeq;
    	DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
    	DDS_Long i = 0;
    
    	helloWorldReader = HelloWorldDataReader::narrow(reader);
    	
    	retcode = helloWorldReader->take(dataSeq, infoSeq, 
    		DDS_LENGTH_UNLIMITED, DDS_ANY_SAMPLE_STATE, 
    		DDS_ANY_VIEW_STATE, DDS_ANY_INSTANCE_STATE);
    
    	for (i = 0; i < dataSeq.length(); ++i) 
    	{
    		if (infoSeq[i].valid_data) {
    		    HelloWorldTypeSupport::print_data(&dataSeq[i]);
    		}
    	}
    	retcode = helloWorldReader->return_loan(dataSeq, infoSeq);
    }

    Note that the sequence received is of type HelloWorldSeq which contains the native plain language objects of type HelloWorld. This can be manipulated directly by the application. For example the fields can be dereferenced as shown in the code snippet below:

    HelloWorld *helloWorldData = &dataSeq[i];
    printf(“count= %s\n”, helloWorldData->count);