C++ API Reference
HDDS provides a modern C++ SDK with RAII wrappers around the C FFI layer. The API uses C++17 features for safe, idiomatic usage.
This documents the current v1.0.0 API. Listeners are supported via hdds_listener.hpp. Instance management and content-filtered topics are not yet implemented.
Installation
# Build from source
cd /path/to/hdds/sdk/cxx
mkdir build && cd build
cmake ..
make -j$(nproc)
# CMake integration
find_package(hdds REQUIRED)
target_link_libraries(myapp hdds::hdds)
Header
#include <hdds.hpp>
Quick Start
#include <hdds.hpp>
int main() {
// RAII participant
hdds::Participant participant("my_app");
// Fluent QoS builder
auto qos = hdds::QoS::reliable()
.transient_local()
.history_depth(10);
// Create writer (raw bytes)
auto writer = participant.create_writer_raw("hello/world", qos);
// Publish
std::vector<uint8_t> data = {1, 2, 3, 4};
writer->write_raw(data);
return 0; // RAII cleanup
}
Participant
Entry point for all DDS operations.
Creation
// Basic creation (domain 0)
hdds::Participant participant("my_app");
// With domain ID
hdds::Participant participant("my_app", 42);
Properties
const std::string& name = participant.name();
uint32_t domain = participant.domain_id();
uint8_t pid = participant.participant_id();
// Get C handle for advanced usage
HddsParticipant* c_handle = participant.c_handle();
Creating Writers/Readers
// Raw (untyped) writer/reader
auto writer = participant.create_writer_raw("topic", qos);
auto reader = participant.create_reader_raw("topic", qos);
// Typed writer/reader (requires CDR2 serialization)
auto writer = participant.create_writer<MyType>("topic", qos);
auto reader = participant.create_reader<MyType>("topic", qos);
// Publisher/Subscriber grouping
auto publisher = participant.create_publisher(qos);
auto subscriber = participant.create_subscriber(qos);
DSCP Configuration
DSCP configuration is implemented in the Rust core but the C++ SDK bindings (set_dscp(), DscpConfig::from_env(), DscpConfig::class_name()) are not yet exported. Use the HDDS_DSCP environment variable or configure via the Rust API directly.
DSCP classes (available in Rust and C APIs):
enum class DscpClass : uint8_t {
BestEffort = 0, // CS0 - Default, no priority
Af11 = 10, // High-throughput data
Af21 = 18, // Low-latency data (standard DDS)
Af31 = 26, // Streaming media
Af41 = 34, // Video, important telemetry
Ef = 46, // Real-time, safety-critical
Cs6 = 48, // Network control
Cs7 = 56, // Highest priority
};
QoS Configuration
Fluent builder API for Quality of Service.
Factory Methods
// Predefined profiles
auto qos = hdds::QoS::default_qos(); // BestEffort, Volatile
auto qos = hdds::QoS::reliable(); // Reliable delivery
auto qos = hdds::QoS::best_effort(); // Fire and forget
auto qos = hdds::QoS::rti_defaults(); // RTI Connext compatible
// Load from XML file
auto qos = hdds::QoS::from_file("fastdds_profile.xml");
Fluent Builder
auto qos = hdds::QoS::reliable()
.transient_local() // Durability
.history_depth(100) // History
.deadline(std::chrono::milliseconds(100)) // Deadline
.lifespan(std::chrono::seconds(5)) // Lifespan
.liveliness_automatic(std::chrono::seconds(1))
.ownership_exclusive(100) // Ownership strength
.partition("sensors") // Partition
.time_based_filter(std::chrono::milliseconds(10))
.transport_priority(10)
.resource_limits(1000, 100, 10); // max_samples, instances, per_instance
Inspection
bool reliable = qos.is_reliable();
bool transient = qos.is_transient_local();
uint32_t depth = qos.get_history_depth();
// Get C handle for FFI
HddsQoS* c_handle = qos.c_handle();
DataWriter
Writers publish data to a topic.
Creation
// Create with default QoS
auto writer = participant.create_writer_raw("topic");
// Create with custom QoS
auto qos = hdds::QoS::reliable().transient_local();
auto writer = participant.create_writer_raw("topic", qos);
Writing Data
// Write raw bytes (vector)
std::vector<uint8_t> data = {1, 2, 3, 4};
writer->write_raw(data);
// Write raw bytes (pointer + size)
uint8_t buffer[256];
writer->write_raw(buffer, sizeof(buffer));
// Typed write (requires T with CDR2 serialization)
MyType msg{.value = 42};
writer->write(msg);
Properties
const std::string& topic = writer->topic_name();
Move Semantics
// Writers are movable, not copyable
auto writer2 = std::move(writer); // OK
// auto writer3 = writer2; // Error: deleted copy constructor
DataReader
Readers receive data from a topic.
Creation
// Create with default QoS
auto reader = participant.create_reader_raw("topic");
// Create with custom QoS
auto qos = hdds::QoS::reliable().history_depth(100);
auto reader = participant.create_reader_raw("topic", qos);
Taking Data
// Take raw bytes (non-blocking)
std::optional<std::vector<uint8_t>> data = reader->take_raw();
if (data) {
// Process data
process(*data);
}
// Typed take
std::optional<MyType> msg = reader->take<MyType>();
if (msg) {
std::cout << "Received: " << msg->value << "\n";
}
Status Condition
// Get status condition for WaitSet
HddsStatusCondition* cond = reader->get_status_condition();
WaitSet
Event-driven waiting for data availability.
Basic Usage
// Create waitset
hdds::WaitSet waitset;
// Create reader and attach condition
auto reader = participant.create_reader_raw("topic");
auto* cond = reader->get_status_condition();
waitset.attach(cond);
// Wait loop
while (running) {
bool triggered = waitset.wait(std::chrono::seconds(1));
if (triggered) {
while (auto data = reader->take_raw()) {
process(*data);
}
}
}
// Cleanup
waitset.detach(cond);
Guard Conditions
// Create guard condition for custom signaling
hdds::GuardCondition guard;
waitset.attach(guard);
// Trigger from another thread
std::thread([&guard]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
guard.set_trigger_value(true);
}).detach();
// Wait will return when guard is triggered
waitset.wait();
// Cleanup
waitset.detach(guard);
Infinite Wait
waitset.wait(); // Blocks until condition triggered
Logging
HDDS uses standard logging via environment variables. No initialization required.
# Set log level via environment
export RUST_LOG=hdds=info
# Debug level
export RUST_LOG=hdds=debug
# Trace specific modules
export RUST_LOG=hdds::rtps=trace,hdds::discovery=debug
Log levels (via RUST_LOG):
error- Errors onlywarn- Warnings and errorsinfo- Informational messagesdebug- Debug outputtrace- Verbose trace output
Telemetry
Built-in metrics collection.
Initialize
// Initialize global metrics
hdds::Metrics metrics = hdds::telemetry::init();
Snapshot
hdds::MetricsSnapshot snap = metrics.snapshot();
std::cout << "Messages sent: " << snap.messages_sent << "\n";
std::cout << "Messages received: " << snap.messages_received << "\n";
std::cout << "Latency P50: " << snap.latency_p50_ms() << "ms\n";
std::cout << "Latency P99: " << snap.latency_p99_ms() << "ms\n";
std::cout << "Latency P99.9: " << snap.latency_p999_ms() << "ms\n";
MetricsSnapshot fields:
struct MetricsSnapshot {
uint64_t timestamp_ns;
uint64_t messages_sent;
uint64_t messages_received;
uint64_t messages_dropped;
uint64_t bytes_sent;
uint64_t latency_p50_ns;
uint64_t latency_p99_ns;
uint64_t latency_p999_ns;
uint64_t merge_full_count;
uint64_t would_block_count;
double latency_p50_ms() const;
double latency_p99_ms() const;
double latency_p999_ms() const;
};
Exporter (HDDS Viewer)
// Start telemetry server for HDDS Viewer
auto exporter = hdds::telemetry::start_exporter("127.0.0.1", 4242);
// ... application runs ...
exporter.stop();
Manual Latency Recording
auto start = std::chrono::steady_clock::now();
// ... operation ...
auto end = std::chrono::steady_clock::now();
metrics.record_latency(
std::chrono::duration_cast<std::chrono::nanoseconds>(start.time_since_epoch()).count(),
std::chrono::duration_cast<std::chrono::nanoseconds>(end.time_since_epoch()).count()
);
Error Handling
// Exceptions
class hdds::Error : public std::runtime_error {
public:
explicit Error(const std::string& msg);
};
// Example
try {
auto writer = participant.create_writer_raw("topic");
writer->write_raw(data);
} catch (const hdds::Error& e) {
std::cerr << "HDDS error: " << e.what() << "\n";
}
Publisher / Subscriber
Optional grouping for writers/readers with shared QoS.
// Create publisher
auto publisher = participant.create_publisher(qos);
// Create subscriber
auto subscriber = participant.create_subscriber(qos);
Complete Example
#include <hdds.hpp>
#include <iostream>
#include <thread>
#include <chrono>
int main() {
// Logging configured via RUST_LOG environment variable
// export RUST_LOG=hdds=info
// Create participant
hdds::Participant participant("example");
// Configure QoS
auto qos = hdds::QoS::reliable()
.transient_local()
.history_depth(10);
// Create writer
auto writer = participant.create_writer_raw("hello/world", qos);
// Create reader
auto reader = participant.create_reader_raw("hello/world", qos);
// Setup WaitSet
hdds::WaitSet waitset;
waitset.attach(reader->get_status_condition());
// Publisher thread
std::thread pub_thread([&writer]() {
for (int i = 0; i < 10; ++i) {
std::string msg = "Hello #" + std::to_string(i);
std::vector<uint8_t> data(msg.begin(), msg.end());
writer->write_raw(data);
std::cout << "Published: " << msg << "\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
// Subscriber loop
for (int received = 0; received < 10;) {
if (waitset.wait(std::chrono::seconds(5))) {
while (auto data = reader->take_raw()) {
std::string msg(data->begin(), data->end());
std::cout << "Received: " << msg << "\n";
++received;
}
}
}
pub_thread.join();
return 0;
}
Thread Safety
- Participant creation/destruction: NOT thread-safe
- Writer/Reader creation: NOT thread-safe
writer->write_raw(): Thread-safe (multiple threads can write)reader->take_raw(): NOT thread-safe (use one reader per thread)- QoS methods: NOT thread-safe
- WaitSet: NOT thread-safe
Using with Typed Data
For typed data, use hdds_gen to generate C++ types with CDR2 serialization:
hddsgen gen cpp Temperature.idl -o temperature.hpp
#include "temperature.hpp"
#include <hdds.hpp>
// Typed writer
auto writer = participant.create_writer<Temperature>("sensors/temp", qos);
writer->write(Temperature{.sensor_id = 1, .value = 23.5f});
// Typed reader
auto reader = participant.create_reader<Temperature>("sensors/temp", qos);
if (auto temp = reader->take<Temperature>()) {
std::cout << "Sensor " << temp->sensor_id << ": " << temp->value << "C\n";
}
Listeners
Listeners provide callback-based notification for DDS entity events.
This is an alternative to the polling-based WaitSet pattern.
Include hdds_listener.hpp for the C++ listener wrappers.
#include <hdds_listener.hpp>
ReaderListener
Base class for DataReader event callbacks. Override the methods you need; unoverridden methods are no-ops. The listener must outlive the reader it is attached to.
class hdds::ReaderListener {
public:
virtual ~ReaderListener() = default;
// Called when new data is available (raw serialized bytes)
virtual void on_data_available(const uint8_t* data, size_t len);
// Called when the reader matches/unmatches with a writer
virtual void on_subscription_matched(const hdds::SubscriptionMatchedStatus& status);
// Called when liveliness of a matched writer changes
virtual void on_liveliness_changed(const hdds::LivelinessChangedStatus& status);
// Called when samples are lost (gap in sequence numbers)
virtual void on_sample_lost(const hdds::SampleLostStatus& status);
// Called when samples are rejected due to resource limits
virtual void on_sample_rejected(const hdds::SampleRejectedStatus& status);
// Called when the requested deadline is missed
virtual void on_deadline_missed(const hdds::DeadlineMissedStatus& status);
// Called when QoS is incompatible with a matched writer
virtual void on_incompatible_qos(const hdds::IncompatibleQosStatus& status);
};
WriterListener
Base class for DataWriter event callbacks. Override the methods you need.
class hdds::WriterListener {
public:
virtual ~WriterListener() = default;
// Called after a sample is successfully written
virtual void on_sample_written(const uint8_t* data, size_t len, uint64_t seq);
// Called when the writer matches/unmatches with a reader
virtual void on_publication_matched(const hdds::PublicationMatchedStatus& status);
// Called when an offered deadline is missed
virtual void on_offered_deadline_missed(uint64_t instance_handle);
// Called when QoS is incompatible with a matched reader
virtual void on_offered_incompatible_qos(uint32_t policy_id, const char* policy_name);
// Called when liveliness is lost (MANUAL_BY_* only)
virtual void on_liveliness_lost();
};
Status Types
struct hdds::SubscriptionMatchedStatus {
uint32_t total_count; // Total cumulative matched publications
int32_t total_count_change; // Change since last callback
uint32_t current_count; // Current number of matched publications
int32_t current_count_change; // Change since last callback
};
struct hdds::PublicationMatchedStatus {
uint32_t total_count;
int32_t total_count_change;
uint32_t current_count;
int32_t current_count_change;
};
struct hdds::LivelinessChangedStatus {
uint32_t alive_count;
int32_t alive_count_change;
uint32_t not_alive_count;
int32_t not_alive_count_change;
};
struct hdds::SampleLostStatus {
uint32_t total_count;
int32_t total_count_change;
};
struct hdds::SampleRejectedStatus {
uint32_t total_count;
int32_t total_count_change;
uint32_t last_reason; // 0=NotRejected, 1=ResourceLimit, 2=InstanceLimit, 3=SamplesPerInstanceLimit
};
struct hdds::DeadlineMissedStatus {
uint32_t total_count;
int32_t total_count_change;
};
struct hdds::IncompatibleQosStatus {
uint32_t total_count;
int32_t total_count_change;
uint32_t last_policy_id;
};
Installing Listeners
Use hdds::set_listener() to install a listener on a reader or writer, and hdds::clear_listener() to remove it. Both are overloaded for readers and writers.
// Install a ReaderListener
hdds::set_listener(reader->c_handle(), &my_reader_listener); // returns 0 on success
// Remove a ReaderListener
hdds::clear_listener(reader->c_handle());
// Install a WriterListener
hdds::set_listener(writer->c_handle(), &my_writer_listener);
// Remove a WriterListener
hdds::clear_listener(writer->c_handle());
Per-Callback Convenience Setters
For simple cases where you only need one callback, use the per-callback setters instead of a full listener class:
// Set a simple on_data_available callback
hdds::set_on_data_available(reader->c_handle(),
[](const uint8_t* data, size_t len, void* ctx) {
std::cout << "Received " << len << " bytes\n";
});
// Set a simple on_subscription_matched callback
hdds::set_on_subscription_matched(reader->c_handle(),
[](const HddsSubscriptionMatchedStatus* status, void* ctx) {
std::cout << "Matched writers: " << status->current_count << "\n";
});
// Set a simple on_publication_matched callback
hdds::set_on_publication_matched(writer->c_handle(),
[](const HddsPublicationMatchedStatus* status, void* ctx) {
std::cout << "Matched readers: " << status->current_count << "\n";
});
Complete Listener Example
#include <hdds.hpp>
#include <hdds_listener.hpp>
#include <iostream>
#include <atomic>
class SensorListener : public hdds::ReaderListener {
std::atomic<uint64_t> received_{0};
public:
void on_data_available(const uint8_t* data, size_t len) override {
received_.fetch_add(1, std::memory_order_relaxed);
std::cout << "Sample received (" << len << " bytes)\n";
}
void on_subscription_matched(const hdds::SubscriptionMatchedStatus& s) override {
std::cout << "Matched writers: " << s.current_count
<< " (total: " << s.total_count << ")\n";
}
void on_sample_lost(const hdds::SampleLostStatus& s) override {
std::cout << "WARNING: " << s.total_count_change << " sample(s) lost\n";
}
uint64_t count() const { return received_.load(std::memory_order_relaxed); }
};
int main() {
hdds::Participant participant("listener_demo");
auto qos = hdds::QoS::reliable().transient_local();
auto reader = participant.create_reader_raw("sensors/temp", qos);
SensorListener listener;
hdds::set_listener(reader->c_handle(), &listener);
// ... application runs; callbacks fire on background threads ...
// Cleanup: remove listener before destroying reader
hdds::clear_listener(reader->c_handle());
return 0;
}
- The listener object must outlive the reader/writer it is attached to.
- Callbacks are invoked from background threads. Implementations should return quickly and must not block.
- Always call
hdds::clear_listener()before destroying the listener object.
Not Yet Implemented (v1.0.0)
| Feature | Status |
|---|---|
| DSCP C++ bindings | Use env var HDDS_DSCP instead |
| Instance management (dispose, unregister) | Not implemented |
| SampleInfo with metadata | Not implemented |
| Content-filtered topics | Not implemented |
Next Steps
- Hello World C++ - Complete tutorial
- C API - Low-level C FFI
- Rust API - Native Rust API