How data is stored or laid out in memory can vary from language to language, compiler to compiler, operating system to operating system, and processor to processor. This combination of language/compiler/operating system/processor is called a platform. Any modern middleware must be able to take data from one specific platform (say C/gcc 3.2.2/Solaris/Sparc) and transparently deliver it to another (for example, Java/JDK 1.6/Windows/Pentium). This process is commonly called serialization/deserialization or marshalling/demarshalling. Messaging products have typically taken one of two approaches to this problem:
The “do nothing” approach is lightweight on its surface but forces you, the user of the middleware API, to consider all data encoding, alignment, and padding issues. The “send everything” alternative results in large amounts of redundant information being sent with every packet, impacting performance.
Figure 1 Self-Describing Messages vs. Type Definitions
Connext DDS exchanges data type definitions, such as field names and types, once at application start-up time. This increases performance and reduces bandwidth consumption compared to the conventional approach, in which each message is self-describing and thus includes a substantial amount of meta-data along with the actual data.
Connext DDS takes an intermediate approach. Just as objects in your application program belong to some data type, DDS data samples sent on the same Topic share a DDS data type. This DDS type defines the fields that exist in the DDS data samples and what their constituent types are; users in the aerospace and defense industries will recognize such type definitions as a form of Interface Definition Document (IDD). Connext DDS stores and propagates this meta-information separately from the individual DDS data samples, allowing it to propagate DDS samples efficiently while handling byte ordering and alignment issues for you.
Figure 2 Example IDD
This example IDD shows one legacy approach to type definition. RTI supports multiple standard type definition formats that are machine readable as well as human readable.
With RTI, you have a number of choices when it comes to defining and using DDS data types. You can choose one of these options, or you can mix and match them—these approaches interoperate with each other and across programming languages and platforms. So, your options are:
Use the built-in DDS types. If a message is simply a string or a buffer of bytes, you can use RTI's built-in DDS types, described in Using Built-in DDS Types.
Define a DDS type at compile-time using a language-independent description language and RTI Code Generator, rtiddsgen, as described in Using DDS Types Defined at Compile Time. This approach offers the strongest compile-time type safety.
OMG IDL. This format is a standard component of both the DDS and CORBA specifications. It describes data types with a C++-like syntax. This format is described in the User’s Manual.
XML schema (XSD), whether independent or embedded in a WSDL file. XSD may be the format of choice for those using Connext DDS alongside or connected to a web services infrastructure. This format is described in User’s Manual.
XML in a DDS-specific format. This XML format is terser, and therefore easier to read and write by hand, than an XSD file. It offers the general benefits of XML—extensibility and ease of integration—while fully supporting DDS-specific data types and concepts. This format is described in the User’s Manual.
The following sections of this document describe each of these models.
Connext DDS provides a set of standard types that are built into the middleware. These DDS types can be used immediately. The supported built-in DDS types are String, KeyedString, Octets, and KeyedOctets. (On Java and .NET platforms, the latter two types are called Bytes and KeyedBytes, respectively; the names are different but the data is compatible across languages.) String and KeyedStrings can be used to send variable-length strings of single-byte characters. Octets and KeyedOctets can be used to send variable-length arrays of bytes.
These built-in DDS types may be sufficient if your data-type needs are simple. If your data is more complex and highly structured, or you want Connext DDS to examine fields within the data for filtering or other purposes, this option may not be appropriate, and you will need to take additional steps to use compile-time types (see Using DDS Types Defined at Compile Time) or dynamic types (see Running with Dynamic DDS Types).
In this section, we define a DDS type at compile time using a language-independent description and RTI Code Generator (rtiddsgen).
RTI Code Generator accepts data-type definitions in a number of formats, such as OMG IDL, XML Schema (XSD), and a DDS-specific format of XML. This makes it easy to integrate Connext DDS with your development processes and IT infrastructure. In this section, we will define DDS types using IDL. (In case you would like to experiment with a different format, rtiddsgen can convert from any supported format to any other: simply pass the arguments -convertToIdl, -convertToXml, -convertToXsd, or -convertToWsdl.)
As described in the Release Notes, some platforms are supported as both a host and a target, while others are only supported as a target. The rtiddsgen tool must be run on a computer that is supported as a host. For target-only platforms, you will need to run rtiddsgen and build the application on a separate host computer.
The following sections will take your through the process of generating example code from your own data type.
Don't worry about how DDS types are defined in detail for now (we cover it in Data Types and DDS Data Samples section of the User's Manual). For this example, just copy and paste the following into a new file, HelloWorld.idl.
const long HELLO_MAX_STRING_SIZE = 256; struct HelloWorld { string<HELLO_MAX_STRING_SIZE> message; };
Next, we will invoke RTI Code Generator (rtiddsgen), which can be found in the $NDDSHOME/bin directory that should already be on your path, to create definitions of your data type in a target programming language, including logic to serialize and deserialize instances of that type for transmission over the network. Then we will build and run the generated code.
For a complete list of the arguments rtiddsgen understands, and a brief description of each of them, run it with the -help argument. More information about rtiddsgen, including its command-line parameters and the list of files it creates, can be found in the User’s Manual.
Note: Running rtiddsgen on a Red Hat Enterprise Linux 4.0 target platform is not supported because it uses an older JRE. You can, however, run rtiddsgen on a newer Linux platform to generate code that can be used on the Red Hat Enterprise Linux 4.0 target.
Generate C++ code from your IDL file with the following command (replace the architecture namei86Linux3gcc4.8.2 with the name of your own architecture):
> rtiddsgen -ppDisable \ -language C++ \ -example i86Linux3gcc4.8.2 \ -replace \ HelloWorld.idl
The generated code publishes identical DDS data samples and subscribes to them, printing the received data to the terminal. Edit the code to modify each DDS data sample before it's published: Open HelloWorld_publisher.cxx. In the code for publisher_main(), locate the "for" loop and add the bold line seen below, which puts "Hello World!" and a consecutive number in each DDS sample that is sent.2If you are using Visual Studio, consider using sprintf_s instead of sprintf: sprintf_s(instance->msg, 128, "Hello World! (%d)", count);
for (count=0; (sample_count == 0) || (count < sample_count); ++count) { printf("Writing HelloWorld, count %d\n", count); /* Modify the data to be sent here */ sprintf(instance->message, "Hello World! (%d)", count); retcode = HelloWorld_writer->write(*instance, instance_handle); if (retcode != DDS_RETCODE_OK) { printf("write error %d\n", retcode); } NDDSUtility::sleep(send_period); }
Generate C++03 or C++11 code from your IDL file with the following command (replace the architecture name i86Linux3gcc4.8.2 with the name of your own architecture):
> rtiddsgen -ppDisable \ -language C++03 \ -example i86Linux3gcc4.8.2 \ -replace \ HelloWorld.idl
Or:
> rtiddsgen -ppDisable \ -language C++11 \ -example i86Linux3gcc4.8.2 \ -replace \ HelloWorld.idl
The only differences between using C++03 or C++11 is that the latter creates a different subscriber example and adds a flag to enable C++11 in supported compilers that require explicit activation. This also activates C++11 features in the DDS API headers. See the API Reference HTML documentation (Modules > Conventions) for more information.
The generated code publishes identical DDS data samples and subscribes to them, printing the received data to the terminal. Edit the code to modify each DDS data sample before it's published: Open HelloWorld_publisher.cxx. In the code for publisher_main(), locate the "for" loop and add the bold line seen below, which puts "Hello World!" and a consecutive number in each DDS sample that is sent.3If you are using Visual Studio, consider using sprintf_s instead of sprintf: sprintf_s(instance->msg, 128, "Hello World! (%d)", count);
for (int count = 0; count < sample_count || sample_count == 0; count++) { // Modify the data to be written here sample.message("Hello World! (" + std::to_string(count) + ")"); std::cout << "Writing MyOtherType, count " << count << "\n"; writer.write(sample); rti::util::sleep(dds::core::Duration(4)); }
Generate Java code from your IDL file with the following command4The argument -ppDisable tells the code generator not to attempt to invoke the C preprocessor (usually cpp on UNIX systems and cl on Windows systems) on the IDL file prior to generating code. In this case, the IDL file contains no preprocessor directives, so no preprocessing is necessary. However, if the preprocessor executable is already on your system path (on Windows systems, running the Visual Studio script vcvars32.bat will do this for you) you can omit this argument. (replace the architecture name i86Linux3gcc4.8.2 with the name of your own architecture):
> rtiddsgen -ppDisable \ -language Java \ -example i86Linux3gcc4.8.2 \ -replace \ HelloWorld.idl
The generated code publishes identical DDS data samples and subscribes to them, printing the received data to the terminal. Edit the code to modify each DDS data sample before it's published: Open HelloWorldPublisher.java. In the code for publisherMain(), locate the "for" loop and add the bold line seen below, which puts "Hello World!" and a consecutive number in each DDS sample that is sent.
for (int count = 0; sampleCount == 0) || (count < sampleCount); ++count) { System.out.println("Writing HelloWorld, count " + count); /* Modify the instance to be written here */ instance.msg = "Hello World! (" + count + ")"; /* Write data */ writer.write(instance, InstanceHandle_t.HANDLE_NIL); try { Thread.sleep(sendPeriodMillis); } catch (InterruptedException ix) { System.err.println("INTERRUPTED"); break; } }
Generate Ada code from your IDL file with the following command (replace the architecture name x64Linux2.6gcc4.4.5 with the name of your own architecture):
rtiddsgen -ppDisable -language Ada \ -example x64Linux2.6gcc4.4.5 -replace HelloWorld.idl
Notes:
for the “Count in 0 .. Sample_Count “loop Put_Line ("Writing HelloWorld, count " & Count'Img); declare Msg : DDS.String := DDS.To_DDS_String ("Hello World! (" & Count'Img & ")"); begin if Instance.message /= DDS.NULL_STRING then Finalize (Instance.message); end if; Standard.DDS.Copy(Instance.message, Msg); end; HelloWorld_Writer.Write ( Instance_Data => Instance, Handle => Instance_Handle'Unchecked_Access); delay Send_Period; end loop;
You have now defined your data type, generated code for it, and customized that code. It's time to compile the example applications.
With the NDDSHOME environment variable set, start Visual Studio and open the rtiddsgen-generated solution file (.sln). Select the Release configuration in the Build toolbar in Visual Studio5The Connext DDS .NET language binding is currently supported for C# and C++/CLI.. From the Build menu, select Build Solution. This will build two projects: <IDL name>_publisher and <IDL name>_subscriber.
Ada support requires a separate add-on product, Ada Language Support.
Use the generated Ada project file to compile an example on any system.
Note: The generated project file assumes the correct version of the compiler is already on your path, NDDSHOME is set, and $NDDSHOME/lib/gnat is in your ADA_PROJECT_PATH.
gmake -f makefile_HelloWorld_<architecture>
After compiling the Ada example, you will find the application executables in the directory, samples/bin. The build command in the generated makefile uses the static release versions of the Connext DDS libraries. To select dynamic or debug versions of the libraries, change the Ada compiler variables RTIDDS_LIBRARY_TYPE and RTIDDS_BUILD in the build command in the makefile to build with the desired version of the libraries. For example, if the application must be compiled with the relocatable debug version of the libraries, compile with the following command:
gprbuild -p -P samples/helloworld-samples.gpr -XOS=Linux \ -XRTIDDS_LIBRARY_TYPE=relocatable -XRTIDDS_BUILD=debug -XARCH=${ARCH}
First, start the subscriber application, HelloWorld_subscriber:Use the generated makefile to compile a C or C++ example on a UNIX-based system or a Java example on any system. Note: the generated makefile assumes the correct version of the compiler is already on your path and that NDDSHOME is set.
gmake -f makefile_HelloWorld_<architecture>
After compiling the C or C++ example, you will find the application executables in a directory objs/<architecture>.
The generated makefile includes the static release versions of the Connext DDS libraries. To select dynamic or debug versions of the libraries, edit the makefile to change the library suffixes. Generally, Connext DDS uses the following convention for library suffixes: "z" for static release, "zd" for static debug, none for dynamic release, and "d" for dynamic debug. For a complete list of the required libraries for each configuration, see the RTI Connext DDS Core Libraries Platform Notes.
For example, to change a C++ makefile from using static release to dynamic release libraries, change this directive:
LIBS = -L$(NDDSHOME)/lib/<architecture> \ -lnddscppz -lnddscz -lnddscorez $(syslibs_<architecture>)
to:
LIBS = -L$(NDDSHOME)/lib/<architecture> \ -lnddscpp -lnddsc -lnddscore $(syslibs_<architecture>)
Run the example publishing and subscribing applications and see them communicate:
First, start the subscriber application, HelloWorld_subscriber:
./objs/<architecture>/HelloWorld_subscriber
In this command window, you should see that the subscriber wakes up every four seconds to print a message:
HelloWorld subscriber sleeping for 4 sec... HelloWorld subscriber sleeping for 4 sec... HelloWorld subscriber sleeping for 4 sec...
Next, open another command prompt window and start the publisher application, HelloWorld_publisher. For example:
./objs/<architecture>/HelloWorld_publisher
In this second (publishing) command window, you should see:
Writing HelloWorld, count 0
Writing HelloWorld, count 1
Writing HelloWorld, count 2
Look back in the first (subscribing) command window. You should see that the subscriber is now receiving messages from the publisher:
HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {0}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {1}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {2}“
You can run the generated applications using the generated makefile. On most platforms, the generated makefile assumes the correct version of java is already on your path and that the NDDSHOME environment variable is set6 One exception on LynxOS systems; see Getting Started on Embedded UNIX-like Systems in the Getting Started Guide, Addendum for Embedded Platforms..
First, run the subscriber:
gmake -f makefile_HelloWorld_<architecture> HelloWorldSubscriber
In this command window, you should see that the subscriber wakes up every four seconds to print a message:
HelloWorld subscriber sleeping for 4 sec... HelloWorld subscriber sleeping for 4 sec... HelloWorld subscriber sleeping for 4 sec...
Next, run the publisher:
gmake -f makefile_HelloWorld_<architecture> HelloWorldPublisher
In this second (publishing) command window, you should see:
Writing HelloWorld, count 0 Writing HelloWorld, count 1 Writing HelloWorld, count 2
Look back in the first (subscribing) command window. You should see that the subscriber is now receiving messages from the publisher:
HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {0}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {1}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {2}“
Ada support requires a separate add-on product, Ada Language Support.
First, start the subscriber application, helloworld_idl_file-helloworld_subscriber:
./samples/bin/helloworld_idl_file-helloworld_subscriber
In this command window, you should see that the subscriber wakes up every four seconds to print a message:
HelloWorld subscriber sleeping for 4.000000000 sec. HelloWorld subscriber sleeping for 4.000000000 sec. HelloWorld subscriber sleeping for 4.000000000 sec.
Next, open another command prompt window and start the publisher application, helloworld_idl_file-helloworld_publisher. For example:
./samples/bin/helloworld_idl_file-helloworld_publisher
In this second (publishing) command window, you should see:
Writing HelloWorld, count 0 Writing HelloWorld, count 1 Writing HelloWorld, count 2
Look back in the first (subscribing) command window. You should see that the subscriber is now receiving messages from the publisher:
HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {0}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {1}“ HelloWorld subscriber sleeping for 4 sec... msg: “Hello World! {2}“
Dynamic DDS types are not supported when using Ada Language Support
This method may be appropriate for applications for which the structure (type) of messages changes frequently or for deployed systems in which newer versions of applications need to interoperate with existing applications that cannot be recompiled to incorporate message-type changes.
As your system evolves, you may find that your data types need to change. And unless your system is relatively small, you may not be able to bring it all down at once in order to modify them. Instead, you may need to upgrade your types one component at a time-or even on the fly, without bringing any part of the system down.
While covering dynamic DDS types is outside the scope of this section, you can learn more about the subject in Chapter 3 of the User's Manual. You can also view and run the Hello World example code located in examples/connext_dds/<language>/Hello_dynamic/src.
For the examples in Modern C++, see the Modern C++ API reference, Modules > Programming How-To's > DynamicType and DynamicData Use Cases.
© 2015 RTI