IDL Compiler Guide

The AstuteDDS IDL compiler (astutedds-idl) translates OMG IDL 4.2 definitions into C++20 code with full X-Types support.

What is IDL?

Interface Definition Language (IDL) is a specification language used to define data types in a language-neutral way. The AstuteDDS IDL compiler generates C++ code from IDL definitions for use with DDS.

Installation

The IDL compiler is built as part of AstuteDDS:

cd astutedds-cxx
mkdir build && cd build
cmake .. -DASTUTEDDS_BUILD_TOOLS=ON
cmake --build . --target astutedds-idl

The compiler binary is located at:

build/tools/idl_compiler/astutedds-idl

Basic Usage

Command Line Syntax

astutedds-idl [options] <input.idl> [output_directory]

Simple Example

# Compile shapes.idl to current directory
./build/tools/idl_compiler/astutedds-idl examples/shapes_demo/shapes.idl

# Compile to specific output directory
./build/tools/idl_compiler/astutedds-idl examples/shapes_demo/shapes.idl generated/

Command Line Options

astutedds-idl --help

Options:
  -o, --output-dir <dir>      Output directory (default: current)
  -l, --language <lang>       Target language (cpp, c, python)
  --xcdr-version <1|2>        XCDR version (default: both)
  --namespace <ns>            Root namespace override
  --type-support              Generate TypeSupport only
  --verbose                   Verbose output
  --version                   Show version
  -h, --help                  Show help

Shapes Demo Example

The Shapes Demo uses a simple IDL file that demonstrates common DDS patterns.

shapes.idl

// File: examples/shapes.idl
module ShapesDemo {

    enum ShapeKind {
        CIRCLE,
        SQUARE,
        TRIANGLE
    };

    @mutable
    struct ShapeType {
        @key string<128> color;
        long x;
        long y;
        long shapesize;
    };

    @final
    struct Point {
        long x;
        long y;
    };

    @appendable
    struct Circle {
        @key long id;
        Point center;
        long radius;
    };
};

Compiling shapes.idl

# Compile the shapes IDL
./build/tools/idl_compiler/astutedds-idl examples/shapes.idl output/

# Generated files:
# output/ShapeKind.hpp          - Enum definition
# output/ShapeType.hpp          - Main struct
# output/ShapeType_TypeSupport.hpp  - DDS TypeSupport
# output/ShapeType_XCDR.hpp     - Serialization code
# output/Point.hpp              - Point struct
# output/Circle.hpp             - Circle struct

Using Generated Code

#include "ShapeType.hpp"
#include "ShapeType_TypeSupport.hpp"
#include <astutedds/dcps/domain_participant.hpp>

int main() {
    using namespace ShapesDemo;

    // Create DDS entities
    auto participant = create_participant(0);
    auto topic = participant->create_topic<ShapeType>("Circle");
    auto writer = create_datawriter(participant, topic);

    // Use generated type
    ShapeType shape;
    shape.color = "BLUE";
    shape.x = 120;
    shape.y = 135;
    shape.shapesize = 30;

    // Write sample
    writer->write(shape);

    return 0;
}

IDL Language Features

Modules (Namespaces)

Modules map to C++ namespaces:

module Sensors {
    module Temperature {
        struct Reading {
            float celsius;
            long timestamp;
        };
    };
};

Generated C++:

namespace Sensors {
namespace Temperature {
    struct Reading {
        float celsius;
        int32_t timestamp;
    };
} // namespace Temperature
} // namespace Sensors

Primitive Types

IDL primitive types map to C++ types:

IDL Type C++ Type Size
boolean bool 1 byte
char char 1 byte
octet uint8_t 1 byte
short int16_t 2 bytes
unsigned short uint16_t 2 bytes
long int32_t 4 bytes
unsigned long uint32_t 4 bytes
long long int64_t 8 bytes
unsigned long long uint64_t 8 bytes
float float 4 bytes
double double 8 bytes
long double long double 16 bytes

Strings

struct Message {
    string text;              // Unbounded string
    string<256> limited;      // Bounded string (max 256 chars)
};

Generated C++:

struct Message {
    std::string text;
    std::string limited;  // Application must enforce length
};

Sequences

struct DataSet {
    sequence<float> values;          // Unbounded sequence
    sequence<long, 100> readings;    // Bounded sequence (max 100)
};

Generated C++:

struct DataSet {
    std::vector<float> values;
    std::vector<int32_t> readings;  // Max size checked at runtime
};

Arrays

struct Matrix {
    float data[4][4];        // 2D array
    long vector[3];          // 1D array
};

Generated C++:

struct Matrix {
    std::array<std::array<float, 4>, 4> data;
    std::array<int32_t, 3> vector;
};

Enumerations

enum Status {
    IDLE,
    RUNNING,
    STOPPED,
    ERROR
};

struct SystemState {
    Status current_status;
};

Generated C++:

enum class Status : int32_t {
    IDLE = 0,
    RUNNING = 1,
    STOPPED = 2,
    ERROR = 3
};

struct SystemState {
    Status current_status{Status::IDLE};
};

Nested Structures

struct Point3D {
    double x;
    double y;
    double z;
};

struct Pose {
    Point3D position;
    Point3D orientation;
};

Generated C++:

struct Point3D {
    double x{0.0};
    double y{0.0};
    double z{0.0};
};

struct Pose {
    Point3D position;
    Point3D orientation;
};

IDL Annotations

Annotations control DDS behavior and code generation.

@key - Instance Identity

Marks fields that identify unique instances:

struct SensorData {
    @key long sensor_id;     // Key field
    float temperature;
    float humidity;
};

Effect:

  • Each unique sensor_id is a separate instance
  • Readers can track instances independently
  • Used for instance lifecycle management

@optional - Optional Fields

Marks fields that may not always be present:

struct Config {
    long id;
    @optional string description;
    @optional float calibration_factor;
};

Generated C++:

struct Config {
    int32_t id{0};
    std::optional<std::string> description;
    std::optional<float> calibration_factor;
};

@id - Member ID

Explicitly assigns member IDs for XCDR2 mutable types:

@mutable
struct Extensible {
    @id(1) long field_a;
    @id(2) string field_b;
    @id(10) @optional float field_c;
};

Benefits:

  • Forward/backward compatibility
  • Fields can be reordered
  • New fields can be added

@autoid - Automatic Member IDs

Generates member IDs automatically:

@mutable
@autoid(SEQUENTIAL)  // or HASH
struct AutoIdType {
    long field1;  // ID assigned automatically
    long field2;
    long field3;
};

Extensibility Annotations

Control type evolution compatibility:

@final
struct Fixed {
    // Cannot add/remove fields
    long id;
    string name;
};

@appendable
struct Growable {
    // Can add fields at end
    long id;
    string name;
};

@mutable
struct Flexible {
    // Can add/remove/reorder fields
    @id(1) long id;
    @id(2) string name;
};

Complete IDL Example

Here's a comprehensive example using various IDL features:

// sensor_system.idl
module SensorSystem {

    // Enumeration for sensor types
    enum SensorType {
        TEMPERATURE,
        PRESSURE,
        HUMIDITY,
        LIGHT
    };

    // Simple status enumeration
    enum Status {
        INACTIVE,
        ACTIVE,
        ERROR
    };

    // Point structure
    struct Location {
        double latitude;
        double longitude;
        @optional double altitude;
    };

    // Sensor reading (mutable for evolution)
    @mutable
    struct SensorReading {
        @key @id(1) long sensor_id;
        @id(2) SensorType type;
        @id(3) float value;
        @id(4) long long timestamp;
        @id(5) @optional Status status;
        @id(6) @optional Location location;
    };

    // Sensor configuration (final - won't change)
    @final
    struct SensorConfig {
        @key long sensor_id;
        string<64> name;
        SensorType type;
        float min_value;
        float max_value;
        long update_rate_ms;
    };

    // Aggregated statistics (appendable)
    @appendable
    struct SensorStatistics {
        @key long sensor_id;
        float mean_value;
        float std_deviation;
        long sample_count;
        sequence<float, 100> recent_values;
    };
};

Compiling the Example

./build/tools/idl_compiler/astutedds-idl sensor_system.idl generated/

Using the Generated Types

#include "generated/SensorReading.hpp"
#include "generated/SensorReading_TypeSupport.hpp"
#include "generated/SensorConfig.hpp"
#include "generated/SensorConfig_TypeSupport.hpp"

int main() {
    using namespace SensorSystem;

    // Create sensor reading
    SensorReading reading;
    reading.sensor_id = 42;
    reading.type = SensorType::TEMPERATURE;
    reading.value = 25.5f;
    reading.timestamp = get_current_time();
    reading.status = Status::ACTIVE;

    Location loc;
    loc.latitude = 51.5074;
    loc.longitude = -0.1278;
    loc.altitude = 11.0;
    reading.location = loc;

    // Create sensor configuration
    SensorConfig config;
    config.sensor_id = 42;
    config.name = "TempSensor-001";
    config.type = SensorType::TEMPERATURE;
    config.min_value = -50.0f;
    config.max_value = 150.0f;
    config.update_rate_ms = 1000;

    // Publish via DDS
    auto participant = create_participant(0);

    auto reading_topic = participant->create_topic<SensorReading>("SensorReadings");
    auto reading_writer = create_datawriter(participant, reading_topic);
    reading_writer->write(reading);

    auto config_topic = participant->create_topic<SensorConfig>("SensorConfigs");
    auto config_writer = create_datawriter(participant, config_topic);
    config_writer->write(config);

    return 0;
}

Generated Files

For each IDL type, the compiler generates:

1. Type Header (TypeName.hpp)

Contains the C++ struct definition:

#ifndef SENSORREADING_HPP
#define SENSORREADING_HPP

#include <cstdint>
#include <string>
#include <optional>

namespace SensorSystem {

struct SensorReading {
    int32_t sensor_id{0};
    SensorType type{SensorType::TEMPERATURE};
    float value{0.0f};
    int64_t timestamp{0};
    std::optional<Status> status;
    std::optional<Location> location;

    bool operator==(const SensorReading&) const = default;
};

} // namespace SensorSystem

#endif

2. TypeSupport Header (TypeName_TypeSupport.hpp)

Provides DDS integration:

#ifndef SENSORREADING_TYPESUPPORT_HPP
#define SENSORREADING_TYPESUPPORT_HPP

#include "SensorReading.hpp"
#include <astutedds/xtypes/type_object.hpp>

namespace SensorSystem {

class SensorReadingTypeSupport {
public:
    static astutedds::xtypes::CompleteTypeObject get_type_object();
    static astutedds::xtypes::TypeIdentifier get_type_identifier();
    static bool register_type(
        astutedds::dcps::DomainParticipant& participant,
        const std::string& type_name = "SensorSystem::SensorReading"
    );
};

} // namespace SensorSystem

#endif

3. XCDR Codec Header (TypeName_XCDR.hpp)

Serialization/deserialization code:

#ifndef SENSORREADING_XCDR_HPP
#define SENSORREADING_XCDR_HPP

#include "SensorReading.hpp"
#include <astutedds/cdr/cdr_types.hpp>

namespace SensorSystem {

class SensorReadingXCDR1 {
public:
    static bool serialize(
        astutedds::cdr::CDRBuffer& buffer,
        const SensorReading& value,
        astutedds::cdr::EncodingKind encoding
    );

    static bool deserialize(
        astutedds::cdr::CDRBuffer& buffer,
        SensorReading& value,
        astutedds::cdr::EncodingKind encoding
    );
};

class SensorReadingXCDR2 {
public:
    static bool serialize(
        astutedds::cdr::CDRBuffer& buffer,
        const SensorReading& value,
        astutedds::cdr::EncodingKind encoding
    );

    static bool deserialize(
        astutedds::cdr::CDRBuffer& buffer,
        SensorReading& value,
        astutedds::cdr::EncodingKind encoding
    );
};

} // namespace SensorSystem

#endif

Advanced Features

Type Inheritance

struct BaseMessage {
    long sequence_number;
    long long timestamp;
};

struct SensorMessage : BaseMessage {
    long sensor_id;
    float value;
};

Unions

union Data switch (long) {
case 1:
    float temperature;
case 2:
    long humidity;
case 3:
    string message;
default:
    octet raw_data;
};

Constants

const long MAX_SENSORS = 100;
const float PI = 3.14159;
const string<32> VERSION = "1.0.0";

struct Config {
    long max_count;  // Can use MAX_SENSORS
};

Best Practices

  1. Use modules: Organize types in namespaces
  2. Add @key annotations: Define instance identity clearly
  3. Use @mutable for evolution: Allow future changes
  4. Bound collections: Use sequence<T, N> instead of sequence<T>
  5. Document types: Add comments for clarity
  6. Version your IDL: Track changes over time
  7. Test generated code: Compile and run tests

Troubleshooting

Common Errors

Error: Unknown type 'Foo'

  • Solution: Define types before using them, or use forward declarations

Error: Circular dependency detected

  • Solution: Break circular references using forward declarations

Error: Invalid @key annotation

  • Solution: Only use @key on struct members, not nested types

Compiler Output

Enable verbose mode for detailed information:

./build/tools/idl_compiler/astutedds-idl --verbose examples/shapes.idl

Next Steps

References