CycloneDDS C# bindings automatically generate IDL files from your C# data structures. This allows seamless interoperability with other DDS implementations and provides control over how your types are represented in the DDS ecosystem.
- How It Works
- Quick Start
- Default Behavior
- Grouping Types in Files
- Legacy Interoperability
- Cross-Assembly Dependencies
- Advanced Scenarios
- Troubleshooting
When you build your project, the CycloneDDS code generator:
- Discovers all DDS types (marked with
[DdsTopic]or[DdsStruct]) - Groups them by target IDL file (based on C# filename or
[DdsIdlFile]attribute) - Resolves dependencies between types and generates
#includedirectives - Emits IDL files to your build output folder (
bin/Debug/net8.0/) - Embeds metadata into your DLL for cross-assembly reference
Key Benefits:
- ✅ Zero Configuration: Works out of the box for 90% of cases
- ✅ Smart Dependencies: Automatically generates
#includestatements - ✅ Cross-Assembly: Types from referenced DLLs "just work"
- ✅ Legacy Friendly: Override module names for C++ interop
using CycloneDDS.Schema;
namespace MyApp.Messages
{
[DdsTopic("SensorData")]
public partial struct SensorData
{
[DdsKey]
public int SensorId;
public double Temperature;
public double Humidity;
}
}Build Output: SensorData.idl in bin/Debug/net8.0/
// Auto-generated IDL
module MyApp {
module Messages {
struct SensorData {
@key int32 SensorId;
double Temperature;
double Humidity;
};
};
};No extra steps needed! The generator uses your C# filename and namespace to organize the IDL.
Rule: C# filename → IDL filename
| C# File | Generated IDL |
|---|---|
Geometry.cs |
Geometry.idl |
SensorData.cs |
SensorData.idl |
CommonTypes.cs |
CommonTypes.idl |
Rule: C# namespace → IDL modules (dots become ::)
| C# Namespace | IDL Modules |
|---|---|
MyApp |
module MyApp { ... } |
Corp.Common.Geo |
module Corp { module Common { module Geo { ... }}} |
Sensors.Temperature |
module Sensors { module Temperature { ... }} |
C# Source: Data/CommonTypes.cs
namespace Corp.Common
{
[DdsStruct]
public partial struct Point3D
{
public double X, Y, Z;
}
}Generated IDL: bin/Debug/net8.0/CommonTypes.idl
module Corp {
module Common {
struct Point3D {
double X;
double Y;
double Z;
};
};
};Use [DdsIdlFile] to group multiple types into a single IDL file.
C# Source: Types/BasicTypes.cs
using CycloneDDS.Schema;
namespace MyApp.Core
{
[DdsStruct]
[DdsIdlFile("CommonDefs")] // Override default
public partial struct Header
{
public int Sequence;
public long Timestamp;
}
[DdsStruct]
[DdsIdlFile("CommonDefs")] // Same file
public partial struct Footer
{
public int Checksum;
}
}Generated IDL: bin/Debug/net8.0/CommonDefs.idl
module MyApp {
module Core {
struct Header {
int32 Sequence;
int64 Timestamp;
};
struct Footer {
int32 Checksum;
};
};
};Result: Both types in one IDL file instead of two separate files.
When integrating with existing C++ DDS systems, you may need to match their IDL structure exactly.
Your C# Code:
using CycloneDDS.Schema;
namespace MyModernApp.Internal // Your C# namespace (organization)
{
[DdsTopic("SystemState")]
[DdsIdlFile("LegacyCore")] // Match legacy filename
[DdsIdlModule("LegacySys::Core")] // Match legacy modules
public partial struct SystemState
{
[DdsKey]
public int SystemId;
public int Status;
public string Description;
}
}Generated IDL: bin/Debug/net8.0/LegacyCore.idl
module LegacySys {
module Core {
struct SystemState {
@key int32 SystemId;
int32 Status;
string Description;
};
};
};Result: Your C# namespace doesn't have to match the legacy C++ structure. Perfect for gradual migration!
Types from referenced assemblies automatically work. No manual configuration needed.
Assembly A: Corp.Common.dll
File: Geometry.cs
using CycloneDDS.Schema;
namespace Corp.Common.Geometry
{
[DdsStruct]
[DdsIdlFile("MathDefs")]
[DdsIdlModule("Math::Geo")]
public partial struct Point3D
{
public double X, Y, Z;
}
}Build Output:
MathDefs.idlinbin/Debug/net8.0/- Metadata embedded in
Corp.Common.dll
Assembly B: Robot.Control.dll (references Corp.Common.dll)
File: Navigation.cs
using CycloneDDS.Schema;
using Corp.Common.Geometry; // Import from Assembly A
namespace Robot.Control
{
[DdsTopic("Trajectory")]
public partial struct Trajectory
{
[DdsKey]
public int RobotId;
public Point3D StartPoint; // Type from Assembly A
public BoundedSeq<Point3D> Waypoints; // Collection from A
}
}Build Output: Navigation.idl in bin/Debug/net8.0/
// Auto-generated IDL
#include "MathDefs.idl" // ← Automatically added!
module Robot {
module Control {
struct Trajectory {
@key int32 RobotId;
Math::Geo::Point3D StartPoint;
sequence<Math::Geo::Point3D> Waypoints;
};
};
};What Happened:
- Generator detected
Point3Dis from referenced assembly - Queried
Corp.Common.dllmetadata to find IDL file name - Generated
#include "MathDefs.idl" - MSBuild copied
MathDefs.idlfrom A to B's output folder
No manual steps! Just reference the assembly and use the types.
namespace MyApp.Types
{
// Uses defaults: MyApp::Types module, Types.idl file
[DdsStruct]
public partial struct BasicHeader
{
public int Sequence;
}
// Same namespace, but different IDL file
[DdsStruct]
[DdsIdlFile("AdvancedTypes")]
public partial struct AdvancedHeader
{
public int Sequence;
public long Timestamp;
public string Source;
}
// Custom module for interop
[DdsStruct]
[DdsIdlFile("AdvancedTypes")] // Grouped together
[DdsIdlModule("Legacy::Header")] // Different module hierarchy
public partial struct LegacyFormat
{
public int Version;
}
}Result:
Types.idlcontainsBasicHeaderinmodule MyApp::TypesAdvancedTypes.idlcontains:AdvancedHeaderinmodule MyApp::TypesLegacyFormatinmodule Legacy::Header
If your assembly graph is App → LibB → LibA, and:
- LibA defines
Point - LibB defines
Line { Point start, end; } - App defines
Shape { Line[] edges; }
Result:
- App's output folder contains:
LibA.idl,LibB.idl,Shape.idl LibB.idlhas#include "LibA.idl"Shape.idlhas#include "LibB.idl"
Everything is copied automatically. No manual include path configuration!
Problem: No .idl file in output folder.
Check:
- Type is marked with
[DdsTopic]or[DdsStruct] - Type is
partial structorpartial class - Build succeeded (check for code generation errors)
Example:
// ❌ Won't generate IDL
public struct MyData { } // Missing [DdsTopic] and not partial
// ✅ Will generate IDL
[DdsTopic("MyData")]
public partial struct MyData { }Error:
Type 'Corp.Common.Point' from 'Corp.Common.dll' is used but has no [DdsIdlMapping].
Ensure the assembly was built with the CycloneDDS code generator.
Solution: Rebuild the referenced assembly with CycloneDDS tooling.
Cause: You're referencing an assembly that:
- Was built without CycloneDDS generator, OR
- Was built with an old version of CycloneDDS that doesn't embed IDL metadata
Fix:
- Rebuild
Corp.Commonproject - Ensure it references
CycloneDDS.Schema - Ensure build runs code generation
Error:
IDL name collision detected:
Type 1: 'MyApp.Data.Point'
Type 2: 'MyApp.Geometry.Point'
Both map to: 'Common.idl' → 'module Shared { struct Point }'
Use [DdsIdlModule] on one or both types to create distinct module paths.
Problem: Two C# types are generating the same IDL structure.
Solution: Use [DdsIdlModule] to differentiate:
namespace MyApp.Data
{
[DdsStruct]
[DdsIdlFile("Common")]
[DdsIdlModule("Shared::Data")] // ← Differentiate
public partial struct Point { }
}
namespace MyApp.Geometry
{
[DdsStruct]
[DdsIdlFile("Common")]
[DdsIdlModule("Shared::Geometry")] // ← Differentiate
public partial struct Point { }
}Result:
module Shared {
module Data {
struct Point { ... };
};
module Geometry {
struct Point { ... };
};
};Problem: Changed [DdsIdlFile("Old")] to [DdsIdlFile("New")], but Old.idl still exists.
Solution: Run Clean Solution before rebuilding.
Why: The generator creates new files but doesn't delete old ones automatically.
Workaround: Add to your .csproj:
<Target Name="CleanGeneratedIdl" BeforeTargets="Clean">
<ItemGroup>
<GeneratedIdl Include="$(OutputPath)*.idl" />
</ItemGroup>
<Delete Files="@(GeneratedIdl)" />
</Target>- Use Defaults: Let the generator infer file/module names when possible
- Group Logically: Use
[DdsIdlFile]to group related types - Document Overrides: Add comments explaining why you override modules
- Rebuild Referenced Assemblies: After updating CycloneDDS tools
- Don't use
.idlextension:[DdsIdlFile("Types")]not[DdsIdlFile("Types.idl")] - Don't use paths:
[DdsIdlFile("Types")]not[DdsIdlFile("../Types")] - Don't use C# syntax in modules:
[DdsIdlModule("A::B")]not[DdsIdlModule("A.B")] - Don't duplicate IDL identities: Check for name collisions
| Attribute | Purpose | Example |
|---|---|---|
[DdsTopic] |
Define a DDS topic | [DdsTopic("SensorData")] |
[DdsStruct] |
Define a helper struct | [DdsStruct] |
[DdsIdlFile] |
Override IDL filename | [DdsIdlFile("CommonTypes")] |
[DdsIdlModule] |
Override module hierarchy | [DdsIdlModule("Legacy::Core")] |
[DdsKey] |
Mark key field | [DdsKey] public int Id; |
- Full Design Document - Comprehensive technical details
- Task Specification - Implementation requirements
- CycloneDDS IDL Reference - IDL language specification
Need Help? If you encounter issues not covered here, please check the design document or open an issue on GitHub.