CycloneDDS.NET 0.1.25

dotnet add package CycloneDDS.NET --version 0.1.25
                    
NuGet\Install-Package CycloneDDS.NET -Version 0.1.25
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CycloneDDS.NET" Version="0.1.25" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CycloneDDS.NET" Version="0.1.25" />
                    
Directory.Packages.props
<PackageReference Include="CycloneDDS.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CycloneDDS.NET --version 0.1.25
                    
#r "nuget: CycloneDDS.NET, 0.1.25"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CycloneDDS.NET@0.1.25
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CycloneDDS.NET&version=0.1.25
                    
Install as a Cake Addin
#tool nuget:?package=CycloneDDS.NET&version=0.1.25
                    
Install as a Cake Tool

FastCycloneDDS C# Bindings

CI NuGet License: MIT

A modern, high-performance, zero-allocation .NET binding for Eclipse Cyclone DDS, with idiomatic C# API.

See detailed technical overview.

Installation

Install the CycloneDDS.NET package from NuGet:

dotnet add package CycloneDDS.NET

This single package includes:

  • Runtime Library: High-performance managed bindings.
  • Native Assets: Pre-compiled ddsc.dll and idlc.exe (Windows x64).
  • Build Tools: Automatic C# code generation during build.

Important: This package relies on native libraries that require the Visual C++ Redistributable for Visual Studio 2022 to be installed on the target system.

Working with Source Code

If you want to build the project from source or contribute:

  1. Clone the repository (recursively, to get the native submodule):

    git clone --recursive https://github.com/pjanec/CycloneDds.NET.git
    cd CycloneDds.NET
    
  2. Build and Test (One-Stop Script): Run the developer workflow script. This will automatically check for native artifacts (building them if missing), build the solution, and run all tests.

    .\build\build-and-test.ps1
    
  3. Requirements:

    • .NET 8.0 SDK
    • Visual Studio 2022 (C++ Desktop Development workload) for native compilation.
    • CMake 3.16+ in your PATH.

Key Features

🚀 Performance Core

  • Zero-Allocation Writes: Custom marshaller writes directly to pooled buffers (ArrayPool) using a C-compatible memory layout.
  • Zero-Copy Reads: Read directly from native DDS buffers using ref struct views, bypassing deserialization.
  • Unified API: Single reader provides both safe managed objects and high-performance zero-copy views.
  • Lazy Deserialization: Only pay the cost of deep-copying objects when you explicitly access .Data.

🧬 Schema & Interoperability

  • Code-First DSL: Define your data types entirely in C# using attributes ([DdsTopic], [DdsKey], [DdsStruct], [DdsQos]). No need to write IDL files manually.
  • Automatic IDL Generation: The build tools automatically generate standard OMG IDL files from your C# classes, ensuring perfect interoperability with other DDS implementations (C++, Python, Java) and tools. See IDL Generation.
  • Auto-Magic Type Discovery: Runtime automatically registers type descriptors based on your schema.
  • IDL Import: Convert existing IDL files into C# DSL automatically using the IdlImporter tool.
  • 100% Native Compliance: Uses Cyclone DDS native serializer for wire compatibility.

🛠️ Developer Experience

  • Auto-Magic Type Discovery: No manual IDL compilation or type registration required.
  • Async/Await: WaitDataAsync for non-blocking, task-based consumers.
  • Client-Side Filtering: High-performance predicates (view => view.Id > 5) compiled to JIT code.
  • Instance Management: O(1) history lookup for keyed topics.
  • Sender Tracking: Identify the source application (Computer, PID, custom app id) of every message.
  • Modern C#: Events, Properties, and generic constraints instead of listeners and pointers.

1. Defining Data (The Schema)

Define your data using standard C# partial structs. The build tools generate the serialization logic automatically.

High-Performance Schema (Zero Alloc)

Use this for high-frequency data (1kHz+).

using CycloneDDS.Schema;

[DdsTopic("SensorData")]
public partial struct SensorData
{
    [DdsKey, DdsId(0)]
    public int SensorId;

    [DdsId(1)]
    public double Value;

    // Fixed-size buffer (maps to char[32]). No heap allocation.
    [DdsId(2)]
    public FixedString32 LocationId; 
}

Convenient Schema (Managed Types)

Use this for business logic where convenience outweighs raw speed.

[DdsStruct] // Helper struct to be used in the topic data struct (can be nested)
public partial struct GeoPoint { public double Lat; public double Lon; }

[DdsTopic("LogEvents")]
[DdsManaged] // Opt-in to GC allocations for the whole type
public partial struct LogEvent
{
    [DdsKey] 
    public int Id;

    // Standard string (Heap allocated)
    public string Message; 
    
    // Standard List (Heap allocated)
    public List<double> History;

    // Nested custom struct
    public GeoPoint Origin;
}

Configuration & QoS

You can define Quality of Service settings directly on the type using the [DdsQos] attribute. The Runtime automatically applies these settings when creating Writers and Readers for this topic.

[DdsTopic("MachineState")]
[DdsQos(
    Reliability = DdsReliability.Reliable,          // Guarantee delivery
    Durability = DdsDurability.TransientLocal,      // Late joiners get the last value
    HistoryKind = DdsHistoryKind.KeepLast,          // Keep only recent data
    HistoryDepth = 1                                // Only the latest sample
)]
public partial struct MachineState
{
    [DdsKey]
    public int MachineId;
    public StateEnum CurrentState;
}

2. Basic Usage

Publishing

using var participant = new DdsParticipant();

// Auto-discovers topic type and registers it
using var writer = new DdsWriter<SensorData>(participant, "SensorData");

// Zero-allocation write path
writer.Write(new SensorData 
{ 
    SensorId = 1, 
    Value = 25.5,
    LocationId = new FixedString32("Factory_A")
});

Subscribing (Polling)

Reading uses a Scope pattern to ensure safety and zero-copy semantics. You "loan" the data, read it, and return it by disposing the scope.

using var reader = new DdsReader<SensorData>(participant, "SensorData");

// POLL FOR DATA
// Returns a "Loan" which manages native memory
using var loan = reader.Take(maxSamples: 10);

// Iterate received data
foreach (var sample in loan)
{
    // Check for valid data (skip metadata-only samples like Dispose events)
    if (sample.IsValid)
    {
        // OPTION A: Simple (Managed)
        // Lazy property triggers deep copy to managed object
        // SensorData data = sample.Data;
        
        // OPTION B: Fast (Zero-Copy)
        // Get a view over native memory. Zero allocations.
        var view = sample.AsView();
        
        Console.WriteLine($"Received: {view.SensorId} = {view.Value}");
    }
    else
    {
        // Handle lifecycle events (e.g., instance disposed)
        Console.WriteLine($"Instance state: {sample.Info.InstanceState}");
    }
}


3. Async/Await (Modern Loop)

Bridge the gap between real-time DDS and .NET Tasks. No blocking threads required.

Console.WriteLine("Waiting for data...");

// Efficiently waits using TaskCompletionSource (no polling loop)
while (await reader.WaitDataAsync())
{
    // Take all available data
    using var scope = reader.Take();
    
    foreach (var sample in scope)
    {
        await ProcessAsync(sample);
    }
}

4. Advanced Filtering

Filter data before you pay the cost of processing it. This implementation uses C# delegates but executes on the raw buffer view, allowing JIT optimizations to make it extremely fast.

// 1. Set a filter predicate on the Reader
// Logic executes during iteration, skipping irrelevant samples instantly.
// Since 'view' is a ref struct reading raw memory, this is Zero-Copy filtering.
reader.SetFilter(view => view.Value > 100.0 && view.LocationId.ToString() == "Lab_1");

// 2. Iterate
using var scope = reader.Take();
foreach (var highValueSample in scope)
{
    // Guaranteed to be > 100.0 and from Lab_1
}

// 3. Update filter dynamically at runtime
reader.SetFilter(null); // Clear filter

5. Instance Management (Keyed Topics)

For systems tracking many objects (fleets, tracks, sensors), efficiently query a specific object's history without iterating the entire database.

// 1. Create a key template for the object we care about
var key = new SensorData { SensorId = 5 };

// 2. Lookup the Handle (O(1) hashing)
DdsInstanceHandle handle = reader.LookupInstance(key);

if (!handle.IsNil)
{
    // 3. Read history for ONLY Sensor 5
    // Ignores Sensor 1, 2, 3... Zero iteration overhead.
    using var history = reader.ReadInstance(handle, maxSamples: 100);
    
    foreach (var snapshot in history)
    {
        Plot(snapshot.Value);
    }
}

6. Sender Tracking (Identity)

Identify exactly which application instance sent a message. Essential for multi-process debugging.

Sender Configuration

var config = new SenderIdentityConfig 
{ 
    AppDomainId = 1, 
    AppInstanceId = 100 
};

// Enable tracking BEFORE creating writers
participant.EnableSenderTracking(config);

// Now, every writer created by this participant automatically broadcasts identity
using var writer = new DdsWriter<LogEvent>(participant, "Logs");

Receiver Usage

// Enable tracking on the reader
reader.EnableSenderTracking(participant.SenderRegistry);

using var scope = reader.Take();
for (int i = 0; i < scope.Count; i++)
{
    // O(1) Lookup of sender info
    // Returns: ComputerName, ProcessName, ProcessId, AppDomainId, etc.
    var sender = scope.GetSender(i); 
    var msg = scope[i];

    if (sender != null)
    {
        Console.WriteLine($"[{sender.ComputerName} : PID {sender.ProcessId}] says: {msg.Message}");
    }
}

7. Status & Discovery

Know when peers connect or disconnect using standard C# Events.

// Writer Side
writer.PublicationMatched += (s, status) => 
{
    if (status.CurrentCountChange > 0)
        Console.WriteLine($"Subscriber connected! Total: {status.CurrentCount}");
    else
        Console.WriteLine("Subscriber lost.");
};

// Reliable Startup (Wait for Discovery)
// Solves the "Lost First Message" problem
await writer.WaitForReaderAsync(TimeSpan.FromSeconds(5));
writer.Write(new Message("Hello")); // Guaranteed to have a route

8. Lifecycle (Dispose & Unregister)

Properly manage the lifecycle of data instances in the Global Data Space.

var key = new SensorData { SensorId = 1 };

// 1. Data is invalid/deleted
// Readers receive InstanceState = NOT_ALIVE_DISPOSED
writer.DisposeInstance(key);

// 2. Writer is shutting down (graceful disconnect)
// Readers receive InstanceState = NOT_ALIVE_NO_WRITERS (if ownership exclusive)
writer.UnregisterInstance(key);

9. Legacy IDL Import

If you have existing DDS systems defined in IDL, you can generate the corresponding C# DSL automatically.

# Import IDL to C#
CycloneDDS.IdlImporter MySystem.idl ./src/Generated

This generates C# [DdsTopic] structs that are binary-compatible with your existing system. See IDL Import Guide for advanced usage including multi-module support.


Examples

Hello World

A complete "Hello World" example that demonstrates creating a topic, publishing, and subscribing in a single application can be found in examples/HelloWorld.

This example is designed to verify the NuGet package installation and basic functionality using the locally built package.

To run it:

  1. Build the packages: .\build\pack.ps1
  2. Run the example:
    cd examples/HelloWorld
    dotnet run
    

Dependencies

The CycloneDDS.NET package bundles these internal components:

  • Managed Libraries: CycloneDDS.Core, CycloneDDS.Schema, CycloneDDS.CodeGen, CycloneDDS.Runtime
  • Native Assets: ddsc.dll (Cyclone DDS), idlc.exe (IDL Compiler), cycloneddsidljson.dll (IDL JSON plugin)

Performance Characteristics

Feature Allocation Cost Performance Note
Write 0 Bytes Uses ArrayPool + NativeArena
Read (View) 0 Bytes Uses .AsView() + Ref Structs
Read (Managed) Allocates Uses .Data (Deep Copy)
Take (Polling) 0 Bytes Uses Loaned Buffers
Filtering 0 Bytes Manual loop filtering with Views
Sender Lookup 0 Bytes O(1) Dictionary Lookup
Async Wait ~80 Bytes One Task per await cycle

Built for speed. Designed for developers.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.25 87 2/23/2026
0.1.4-alpha-g53bfcf1709 81 2/15/2026

Native Version Information:
- Based on Eclipse Cyclone DDS (https://github.com/eclipse-cyclonedds/cyclonedds) commit: c49206be5cfbe76de546e0adad172a0d80726f77
- Modified from https://github.com/pjanec/cyclonedds.git commit: 2e0c687098733602477c34c7c9874a39e146b807