Skip to main content

Hello World in C

This tutorial demonstrates using the HDDS C FFI bindings for basic publish-subscribe communication.

Prerequisites: HDDS C library installed

Low-Level API

The C API operates on raw CDR-encoded bytes. For typed data, use hdds_gen to generate serialization code, or use the Rust API directly.

Step 1: Project Setup

Create a project directory:

mkdir hdds-hello-c
cd hdds-hello-c

Copy the HDDS header:

# From HDDS source
cp /path/to/hdds/crates/hdds-c/hdds.h .

Step 2: Create the Publisher

Create publisher.c:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "hdds.h"

int main(void) {
printf("Starting publisher...\n");

// Initialize logging (optional)
hdds_logging_init(INFO);

// Create participant with UDP multicast transport
struct HddsParticipant *participant =
hdds_participant_create_with_transport("publisher", UDP_MULTICAST);
if (participant == NULL) {
fprintf(stderr, "Failed to create participant\n");
return 1;
}
printf("Participant created: %s\n", hdds_participant_name(participant));

// Create QoS profile (reliable, transient-local)
struct HddsQoS *qos = hdds_qos_reliable();
hdds_qos_set_transient_local(qos);
hdds_qos_set_history_depth(qos, 10);

// Create writer with QoS
struct HddsDataWriter *writer =
hdds_writer_create_with_qos(participant, "hello/world", qos);
if (writer == NULL) {
fprintf(stderr, "Failed to create writer\n");
hdds_qos_destroy(qos);
hdds_participant_destroy(participant);
return 1;
}
printf("Writer created on topic: hello/world\n");

// QoS no longer needed after writer creation
hdds_qos_destroy(qos);

// Give time for discovery
printf("Waiting for discovery...\n");
sleep(2);

// Publish messages (raw bytes)
for (int i = 0; i < 10; i++) {
char message[64];
int len = snprintf(message, sizeof(message), "Hello #%d from C!", i);

enum HddsError result = hdds_writer_write(writer, message, len);
if (result == OK) {
printf("Published: %s\n", message);
} else {
fprintf(stderr, "Write failed: %d\n", result);
}

sleep(1);
}

// Cleanup
printf("Cleaning up...\n");
hdds_writer_destroy(writer);
hdds_participant_destroy(participant);
printf("Done.\n");

return 0;
}

Step 3: Create the Subscriber

Create subscriber.c:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "hdds.h"

int main(void) {
printf("Starting subscriber...\n");

// Initialize logging
hdds_logging_init(INFO);

// Create participant
struct HddsParticipant *participant =
hdds_participant_create_with_transport("subscriber", UDP_MULTICAST);
if (participant == NULL) {
fprintf(stderr, "Failed to create participant\n");
return 1;
}
printf("Participant created\n");

// Create QoS (must be compatible with writer)
struct HddsQoS *qos = hdds_qos_reliable();
hdds_qos_set_history_depth(qos, 100);

// Create reader with QoS
struct HddsDataReader *reader =
hdds_reader_create_with_qos(participant, "hello/world", qos);
if (reader == NULL) {
fprintf(stderr, "Failed to create reader\n");
hdds_qos_destroy(qos);
hdds_participant_destroy(participant);
return 1;
}
printf("Reader created on topic: hello/world\n");
hdds_qos_destroy(qos);

// Set up WaitSet for event-driven reading
struct HddsWaitSet *waitset = hdds_waitset_create();
const struct HddsStatusCondition *condition =
hdds_reader_get_status_condition(reader);
hdds_waitset_attach_status_condition(waitset, condition);

printf("Waiting for data...\n");

// Read loop
char buffer[256];
size_t len_read;
const void *triggered[4];
size_t triggered_count;

while (true) {
// Wait for data (5 second timeout)
int64_t timeout_ns = 5LL * 1000 * 1000 * 1000;
enum HddsError wait_result = hdds_waitset_wait(
waitset, timeout_ns, triggered, 4, &triggered_count);

if (wait_result == OK && triggered_count > 0) {
// Take all available samples
while (hdds_reader_take(reader, buffer, sizeof(buffer) - 1, &len_read) == OK) {
buffer[len_read] = '\0';
printf("Received: %s\n", buffer);
}
} else {
printf("No data received (timeout)\n");
}
}

// Cleanup (not reached in this example)
hdds_waitset_detach_condition(waitset, condition);
hdds_status_condition_release(condition);
hdds_waitset_destroy(waitset);
hdds_reader_destroy(reader);
hdds_participant_destroy(participant);

return 0;
}

Step 4: Create Makefile

Create Makefile:

CC = gcc
CFLAGS = -Wall -Wextra -O2
LDFLAGS = -L/usr/local/lib -lhdds_c -lpthread -ldl -lm

all: publisher subscriber

publisher: publisher.c hdds.h
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)

subscriber: subscriber.c hdds.h
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)

clean:
rm -f publisher subscriber

.PHONY: all clean

Step 5: Build and Run

# Build
make

# Terminal 1 - Start subscriber
./subscriber

# Terminal 2 - Start publisher
./publisher

Expected Output

Subscriber:

Starting subscriber...
Participant created
Reader created on topic: hello/world
Waiting for data...
Received: Hello #0 from C!
Received: Hello #1 from C!
Received: Hello #2 from C!
...

Publisher:

Starting publisher...
Participant created: publisher
Writer created on topic: hello/world
Waiting for discovery...
Published: Hello #0 from C!
Published: Hello #1 from C!
...
Done.

C API Reference

Participant

// Create with default transport (UDP multicast)
struct HddsParticipant *p = hdds_participant_create("my_app");

// Create with specific transport
struct HddsParticipant *p = hdds_participant_create_with_transport(
"my_app",
UDP_MULTICAST // or INTRA_PROCESS
);

// Properties
const char *name = hdds_participant_name(p);
uint32_t domain = hdds_participant_domain_id(p);

// Cleanup
hdds_participant_destroy(p);

Writer

// Create with default QoS
struct HddsDataWriter *w = hdds_writer_create(participant, "topic_name");

// Create with custom QoS
struct HddsDataWriter *w = hdds_writer_create_with_qos(participant, "topic_name", qos);

// Write raw bytes
enum HddsError result = hdds_writer_write(writer, data_ptr, data_len);

// Cleanup
hdds_writer_destroy(w);

Reader

// Create with default QoS
struct HddsDataReader *r = hdds_reader_create(participant, "topic_name");

// Create with custom QoS
struct HddsDataReader *r = hdds_reader_create_with_qos(participant, "topic_name", qos);

// Take data (removes from cache)
char buffer[1024];
size_t len_out;
enum HddsError result = hdds_reader_take(reader, buffer, sizeof(buffer), &len_out);

// Cleanup
hdds_reader_destroy(r);

QoS

// Create QoS profiles
struct HddsQoS *qos = hdds_qos_default(); // Best effort, volatile
struct HddsQoS *qos = hdds_qos_best_effort(); // Explicit best effort
struct HddsQoS *qos = hdds_qos_reliable(); // Reliable delivery

// Configure QoS
hdds_qos_set_history_depth(qos, 10);
hdds_qos_set_history_keep_all(qos);
hdds_qos_set_transient_local(qos);
hdds_qos_set_volatile(qos);
hdds_qos_set_reliable(qos);
hdds_qos_set_best_effort(qos);
hdds_qos_set_deadline_ns(qos, 100000000); // 100ms

// Load from XML
struct HddsQoS *qos = hdds_qos_from_xml("/path/to/profile.xml");

// Cleanup
hdds_qos_destroy(qos);

WaitSet

// Create waitset
struct HddsWaitSet *ws = hdds_waitset_create();

// Get reader's status condition
const struct HddsStatusCondition *cond = hdds_reader_get_status_condition(reader);

// Attach condition
hdds_waitset_attach_status_condition(ws, cond);

// Wait for events
const void *triggered[10];
size_t count;
int64_t timeout_ns = 1000000000; // 1 second
enum HddsError result = hdds_waitset_wait(ws, timeout_ns, triggered, 10, &count);

// Cleanup
hdds_waitset_detach_condition(ws, cond);
hdds_status_condition_release(cond);
hdds_waitset_destroy(ws);

Error Codes

typedef enum HddsError {
OK = 0,
INVALID_ARGUMENT = 1,
NOT_FOUND = 2,
OPERATION_FAILED = 3,
OUT_OF_MEMORY = 4,
} HddsError;

Logging

// Initialize with level
hdds_logging_init(INFO); // OFF, ERROR, WARN, INFO, DEBUG, TRACE

// Or with environment variable (RUST_LOG)
hdds_logging_init_env(INFO);

// Or with filter string
hdds_logging_init_with_filter("hdds=debug");

Using with Typed Data (hdds_gen)

For typed data, use hdds_gen to generate C structs and CDR2 serialization functions:

Step 1: Define IDL

Create temperature.idl:

struct Temperature {
uint32 sensor_id;
float value;
string unit;
};

Step 2: Generate C Code

hddsgen gen c temperature.idl -o temperature.h

This generates a header-only file with:

  • C struct definitions
  • temperature_encode_cdr2_le() - serialize to CDR2 bytes
  • temperature_decode_cdr2_le() - deserialize from CDR2 bytes

Step 3: Use Generated Code

#include "temperature.h"
#include "hdds.h"

int main(void) {
// Create participant and writer...
struct HddsParticipant *participant =
hdds_participant_create_with_transport("sensor", UDP_MULTICAST);
struct HddsDataWriter *writer =
hdds_writer_create(participant, "sensors/temperature");

// Create typed data
Temperature temp = {0};
temp.sensor_id = 42;
temp.value = 23.5f;
temp.unit = "celsius";

// Encode to CDR2 bytes
uint8_t buffer[256];
int encoded_len = temperature_encode_cdr2_le(&temp, buffer, sizeof(buffer));
if (encoded_len < 0) {
fprintf(stderr, "Encode failed: %d\n", encoded_len);
return 1;
}

// Write encoded bytes
hdds_writer_write(writer, buffer, (size_t)encoded_len);

// ... cleanup
return 0;
}

Step 4: Decode on Subscriber

#include "temperature.h"
#include "hdds.h"

// In subscriber:
uint8_t buffer[256];
size_t len_read;

if (hdds_reader_take(reader, buffer, sizeof(buffer), &len_read) == OK) {
Temperature temp = {0};
// Pre-allocate buffer for string field
char unit_buf[32];
temp.unit = unit_buf;

int decoded = temperature_decode_cdr2_le(&temp, buffer, len_read);
if (decoded > 0) {
printf("Sensor %u: %.1f %s\n",
temp.sensor_id, temp.value, temp.unit);
}
}
String Fields

For struct fields that are string, the decoder does not allocate memory. You must pre-allocate the buffer and assign the pointer before calling decode.

What's Next?