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

For package users, astutedds-idl is installed with the AstuteDDS developer package.

astutedds-idl --help

If you are building from source, build the tool target:

Compile shapes.idl to current directory

astutedds-idl examples/shapes_demo/shapes.idl mkdir build && cd build cmake .. -DASTUTEDDS_BUILD_TOOLS=ON cmake --build . --target astutedds-idl


Source-build binary location:
build/tools/idl_compiler/astutedds-idl

## Basic Usage

### Command Line Syntax

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

Simple Example

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

# Compile to specific output directory
astutedds-idl -o generated examples/shapes_demo/shapes.idl

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
astutedds-idl -o output examples/shapes_demo/shapes.idl

# 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

Minimum Files for First Application

For the first publisher/subscriber workflow, use:

  • generated/<Type>.hpp
  • generated/<Type>.cpp

Additional generated files such as *_TypeSupport.hpp and *_XCDR.hpp are part of broader type support and advanced integration workflows.

Using Generated Code

For an end-to-end generated type workflow (IDL -> C++ -> publisher/subscriber), follow First Application.

The generated headers are then included by your app code:

#include "ShapeType.hpp"

ShapesDemo::ShapeType sample;
// Populate fields and publish using the DCPS API used in First Application.

IDL Language Features

Troubleshooting

Command Not Found

If astutedds-idl is not found, verify the developer package installation and that your shell PATH includes the package binary directory.

Parse or Syntax Errors

Use verbose mode to pinpoint the failing construct:

astutedds-idl --verbose <input.idl>

Then check for:

  • Unclosed braces or semicolons
  • Invalid annotations or annotation placement
  • Unsupported/invalid IDL tokens

Generated Files Not Found in Build

Verify your CMake target includes both generated artifacts:

  • generated/<Type>.hpp
  • generated/<Type>.cpp

Also ensure your target include path contains the generated directory.

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