Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Devolutions/IronRDP/llms.txt

Use this file to discover all available pages before exploring further.

IronRDP FFI

Foreign Function Interface (FFI) bindings for IronRDP, built using Diplomat. These bindings enable IronRDP usage from other programming languages.

Overview

The IronRDP FFI provides a C-compatible API layer that can be consumed by various programming languages. Currently, .NET is the officially supported target platform with full bindings and examples. Source: ffi/

Supported Platforms

.NET (Official)

  • Namespace: Devolutions.IronRdp
  • Package: Available as NuGet package
  • Platforms: Windows, macOS, Linux, iOS
  • Language: C#

Other Languages

The Diplomat-based FFI can theoretically support:
  • C/C++
  • JavaScript (via WebAssembly)
  • Other languages with C interop
However, only .NET bindings are officially maintained and tested.

Building

Prerequisites

  1. Rust toolchain (1.88.0 or later)
  2. Diplomat tool: cargo xtask ffi install
  3. .NET SDK (for .NET bindings)

Build Steps

# Install required tools
cargo xtask ffi install

# Build the native library
cargo xtask ffi build

# Or for release builds
cargo xtask ffi build --release

# Generate bindings
cargo xtask ffi bindings
This produces:
  • target/debug/libdevolutions_ironrdp.{so|dylib|dll} - Native library
  • ffi/dotnet/Devolutions.IronRdp/Generated/ - C# bindings

.NET API

Installation

dotnet add package Devolutions.IronRdp

Core Modules

The FFI is organized into the following modules:
ModuleDescription
connectorConnection establishment and protocol negotiation
sessionActive RDP session management
inputKeyboard and mouse input handling
graphicsGraphics rendering and pointer handling
clipboardClipboard redirection (CLIPRDR)
pduProtocol Data Unit encoding/decoding
errorError types and handling
utilsUtility types and helpers
dvcDynamic Virtual Channel support
svcStatic Virtual Channel support
rdcleanpathRDCleanPath protocol support
credsspCredSSP/NLA authentication

Connection Establishment

ClientConnector

The ClientConnector class manages the RDP connection sequence.
using Devolutions.IronRdp;

// Create configuration
var config = new Config(
    username: "user",
    password: "password",
    domain: "DOMAIN",
    desktopWidth: 1920,
    desktopHeight: 1080
);

// Create connector
var connector = new ClientConnector(config, "127.0.0.1:54321");

// Attach static channels
connector.WithStaticChannelRdpSnd();
connector.WithStaticChannelRdpdr("COMPUTER", smartCardDeviceId: 0);

// Configure dynamic channels
connector.WithDynamicChannelDisplayControl();
connector.WithDynamicChannelPipeProxy(proxyConfig);

// Perform connection sequence
var state = connector.GetDynState();
while (!state.IsTerminal())
{
    var writeBuf = new WriteBuf();
    var written = connector.Step(inputData, writeBuf);
    
    // Handle security upgrade if needed
    if (connector.ShouldPerformSecurityUpgrade())
    {
        // Perform TLS handshake
        connector.MarkSecurityUpgradeAsDone();
    }
    
    // Handle CredSSP if needed
    if (connector.ShouldPerformCredssp())
    {
        // Perform NLA authentication
        connector.MarkCredsspAsDone();
    }
    
    state = connector.GetDynState();
}

// Get connection result
var result = connector.ConsumeAndCastToClientConnectorState();

Config

Connection configuration parameters.
var config = new Config(
    username: "user",
    password: "password",
    domain: null,  // Optional
    desktopWidth: 1920,
    desktopHeight: 1080
);

// Additional configuration
config.EnableTls = true;
config.EnableCredssp = true;
config.KeyboardType = KeyboardType.IbmEnhanced;
config.EnableAudioPlayback = false;

Session Management

ActiveStage

Represents an active RDP session after connection is established.
// Create from connection result
var activeStage = new ActiveStage(connectionResult);

// Process incoming RDP frames
var image = new DecodedImage(PixelFormat.RgbA32, width, height);
var outputs = activeStage.Process(image, action, payload);

// Handle outputs
foreach (var output in outputs)
{
    switch (output.GetEnumType())
    {
        case ActiveStageOutputType.ResponseFrame:
            var frame = output.GetResponseFrame();
            // Send frame to server
            break;
            
        case ActiveStageOutputType.GraphicsUpdate:
            var rect = output.GetGraphicsUpdate();
            // Update display region
            break;
            
        case ActiveStageOutputType.PointerBitmap:
            var pointer = output.GetPointerBitmap();
            // Update cursor image
            break;
            
        case ActiveStageOutputType.Terminate:
            var reason = output.GetTerminate();
            // Handle disconnection
            break;
    }
}

FastPath Input

Send keyboard and mouse input to the remote session.
// Create input events
var events = new FastPathInputEventIterator();
events.Add(FastPathInputEvent.KeyPressed(scancode));
events.Add(FastPathInputEvent.MouseMove(x, y));
events.Add(FastPathInputEvent.MouseButtonPressed(button));

// Process input
var outputs = activeStage.ProcessFastpathInput(image, events);

Graphics and Rendering

DecodedImage

Represents the decoded RDP framebuffer.
// Create image buffer
var image = new DecodedImage(
    PixelFormat.RgbA32,
    width: 1920,
    height: 1080
);

// Access pixel data
var data = image.GetData();
var stride = image.GetStride();

// Copy region to display buffer
var region = output.GetGraphicsUpdate();
// Use data[region.Top * stride + region.Left] to access pixels

DecodedPointer

Represents a cursor/pointer bitmap.
var pointer = output.GetPointerBitmap();

var width = pointer.GetWidth();
var height = pointer.GetHeight();
var hotspotX = pointer.GetHotspotX();
var hotspotY = pointer.GetHotspotY();
var data = pointer.GetData(); // RGBA pixel data

Clipboard Redirection

Cliprdr

Manages clipboard synchronization between local and remote sessions.
// Create clipboard backend
var backend = new YourClipboardBackend();
var cliprdr = new Cliprdr(backend);

// Attach to connector
connector.AttachStaticCliprdr(cliprdr);

// Later in active stage:
// Initiate copy from local to remote
var formats = new ClipboardFormatIterator();
formats.Add(ClipboardFormat.UnicodeText);
formats.Add(ClipboardFormat.Html);
var frame = activeStage.InitiateClipboardCopy(formats);

// Initiate paste from remote to local
var formatId = new ClipboardFormatId(ClipboardFormatId.CF_UNICODETEXT);
var frame = activeStage.InitiateClipboardPaste(formatId);

// Submit format data response
var response = new FormatDataResponse(data);
var frame = activeStage.SubmitClipboardFormatData(response);

Clipboard Formats

Standard clipboard format IDs:
  • CF_TEXT (1): Plain text (ANSI)
  • CF_UNICODETEXT (13): Unicode text
  • CF_DIB (8): Device-independent bitmap
  • CF_DIBV5 (17): DIB with color space
  • Custom formats: Use registered format IDs (> 0xC000)

Dynamic Virtual Channels

Display Control

Enable dynamic resolution changes.
// Enable during connection
connector.WithDynamicChannelDisplayControl();

// Later, request resize
var outputs = activeStage.EncodedResize(width: 2560, height: 1440);

DVC Pipe Proxy

Proxy DVC traffic to named pipes (Windows).
var config = new DvcPipeProxyConfig();
config.AddDescriptor(new DvcPipeProxyDescriptor
{
    ChannelName = "custom-dvc",
    PipeName = @"\\.\pipe\my-pipe"
});

connector.WithDynamicChannelPipeProxy(config);

// Receive messages
messageSink.OnMessage((channelId, data) => {
    // Handle DVC message
});

// Send messages
var message = new DvcPipeProxyMessage(channelId, data);
var frame = activeStage.SendDvcPipeProxyMessage(message);

Error Handling

IronRdpError

All FFI methods return Result<T, IronRdpError> which throws exceptions in .NET.
try
{
    var connector = new ClientConnector(config, clientAddr);
}
catch (IronRdpException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
    Console.WriteLine($"Kind: {ex.Kind}");
}

Error Kinds

public enum IronRdpErrorKind
{
    Generic,
    PduError,
    EncodeError,
    DecodeError,
    CredsspError,
    Consumed,
    IO,
    AccessDenied,
    IncorrectEnumType,
    Clipboard,
    WrongOS
}

PDU Handling

Action and WriteBuf

Low-level PDU processing.
// Determine action from incoming data
var action = Action.FromBytes(data);

// Allocate output buffer
var writeBuf = new WriteBuf();

// Process with action
var written = connector.Step(data, writeBuf);

// Get encoded output
var outputData = writeBuf.GetData();

PduHint

Used for frame detection in raw byte streams.
var hint = connector.NextPduHint();
if (hint != null)
{
    var sizeResult = hint.FindSize(incomingBytes);
    if (sizeResult.HasValue)
    {
        var frameSize = sizeResult.Value;
        // Read exactly frameSize bytes
    }
}

Utilities

Common Types

// Optional values
public class OptionalUsize
{
    public bool HasValue { get; }
    public usize Value { get; }
}

// Positions
public struct Position
{
    public ushort X { get; set; }
    public ushort Y { get; set; }
}

// Rectangles
public struct InclusiveRectangle
{
    public ushort Left { get; set; }
    public ushort Top { get; set; }
    public ushort Right { get; set; }
    public ushort Bottom { get; set; }
}

// Byte slices
public class BytesSlice
{
    public byte[] GetData();
}

// Byte vectors
public class VecU8
{
    public byte[] GetData();
}

Platform-Specific Features

Windows Clipboard

Native Windows clipboard integration.
#if WINDOWS
var backend = new WinCliprdrBackend();
var cliprdr = new Cliprdr(backend);
connector.AttachStaticCliprdr(cliprdr);
#endif

iOS Support

The NuGet package includes iOS bindings with platform-specific properties:
  • Devolutions.IronRdp.iOS.props
  • Devolutions.IronRdp.Build.iOS.props

Examples

Connect Example

cd ffi/dotnet
dotnet run --project Devolutions.IronRdp.ConnectExample
A console application demonstrating basic RDP connection establishment.

Avalonia Example

cd ffi/dotnet
dotnet run --project Devolutions.IronRdp.AvaloniaExample
A full GUI application using Avalonia framework with:
  • RDP session rendering
  • Input handling
  • Clipboard integration
  • Resize support

Memory Management

Ownership

The FFI uses Rust’s ownership model:
  • Most objects are returned as Box<T> (owned pointers)
  • Some objects are consumed by methods (marked with &mut and take())
  • Objects are automatically freed when disposed

Consumed Values

Some operations consume their inputs:
// This consumes the connector
var state = connector.ConsumeAndCastToClientConnectorState();

// connector is now invalid - don't use it again
Methods that consume values are clearly documented and will return Consumed errors if called on already-consumed objects.

Thread Safety

The FFI is not thread-safe by default. All operations on a given RDP session must be performed from a single thread. For multi-threaded scenarios, implement your own synchronization.

Performance Considerations

  • Zero-copy where possible: The FFI minimizes data copying using byte slices
  • Preallocate buffers: Reuse WriteBuf instances to reduce allocations
  • Batch input events: Use FastPathInputEventIterator to send multiple events at once

Diplomat Configuration

The .NET bindings are configured via dotnet-interop-conf.toml:
namespace = "Devolutions.IronRdp"
native_lib = "DevolutionsIronRdp"

[exceptions]
trim_suffix = "Error"
error_message_method = "ToDisplay"

[properties]
setters_prefix = "set_"
getters_prefix = "get_"

Troubleshooting

Library Not Found

Ensure the native library is in the correct location:
  • Windows: DevolutionsIronRdp.dll
  • macOS: libDevolutionsIronRdp.dylib
  • Linux: libDevolutionsIronRdp.so
The NuGet package includes platform-specific native libraries that are automatically deployed.

Value Consumed Errors

If you see “value is consumed” errors:
  1. Check if you’ve called a consuming method
  2. Don’t reuse objects after consumption
  3. Some methods like Step() are non-consuming and can be called multiple times

IncorrectEnumType Errors

These occur when calling variant-specific methods on enum-like types:
// Wrong - output might not be ResponseFrame
var frame = output.GetResponseFrame(); // Throws if wrong type

// Right - check type first
if (output.GetEnumType() == ActiveStageOutputType.ResponseFrame)
{
    var frame = output.GetResponseFrame(); // Safe
}

Future Development

Planned improvements:
  • JavaScript/TypeScript bindings via Diplomat
  • Python bindings
  • Additional platform support (Android, more .NET platforms)
  • Async API variants