EyeAuras.CLink 0.1.11

CLink Usage From C And C#

CLink has one caller model in both languages: pass an absolute endpoint URL, open an endpoint, move opaque byte messages, and close handles when done.

The URL scheme selects the backend. CLink core does not care whether bytes are RPC, setup messages, telemetry, or payloads.

Native packages are also published as reduced backend variants. The API does not change: if a built-in backend was not compiled into the selected native library, that scheme returns NotImplemented instead of falling back to another backend.

Address Examples

file:///C:/temp/ea-clink/service.ctl?maxPacketSize=65536
mem://pid/12345/0x000001ABCD000000?blockSize=256&maxPacketSize=65536
pipe://localhost/EA_CLink/session/12?protocol=clink-packet-v1&maxPacketSize=65536
shared-mem://Local/EA_CLink_abc123?mapSize=1048576&session=12&epoch=4&channel=3&role=a&sync=Local/EA_CLink_abc123_sync&maxPacketSize=65536

Do not pass friendly names such as pipe://agent or mem://service-name. A URL must contain the backend-specific details needed to open the exact endpoint.

C Snippet Helpers

The C ABI is intentionally explicit. The examples below use small helpers so the real operation sequence stays visible.

#include "clink.h"
#include <stdint.h>
#include <string.h>

static clink_utf8_span clink_text(const char* text)
{
    clink_utf8_span span = { (const uint8_t*) text, (uint64_t) strlen(text) };
    return span;
}

static clink_listener_options clink_listener_url(const char* address)
{
    clink_listener_options options = { sizeof(options), CLINK_ABI_VERSION };
    options.address = clink_text(address);
    return options;
}

static clink_session_options clink_session_url(const char* address)
{
    clink_session_options options = { sizeof(options), CLINK_ABI_VERSION };
    options.address = clink_text(address);
    return options;
}

static clink_connection_options clink_connection_wait(uint32_t timeout_ms)
{
    clink_connection_options options = { sizeof(options), CLINK_ABI_VERSION };
    options.timeout_ms = timeout_ms;
    return options;
}

Core Transport

Use this when a listener address can create a session and channel. file:// and same-process mem:// use this shape today.

C

typedef struct clink_demo_pair
{
    clink_runtime_handle runtime;
    clink_listener_handle listener;
    clink_session_handle session;
    clink_connection_handle client;
    clink_connection_handle server;
} clink_demo_pair;

static void clink_demo_pair_close(clink_demo_pair* pair)
{
    if (pair->server) (void) clink_connection_close(pair->server);
    if (pair->client) (void) clink_connection_close(pair->client);
    if (pair->session) (void) clink_session_close(pair->session);
    if (pair->listener) (void) clink_listener_close(pair->listener);
    if (pair->runtime) (void) clink_runtime_destroy(pair->runtime);
}

clink_status core_transport_example(void)
{
    const char* address = "file:///C:/temp/ea-clink/demo.ctl?maxPacketSize=65536";
    clink_demo_pair pair = { 0 };
    clink_status status = CLINK_STATUS_OK;

    /* 1. Create the process-local CLink owner. */
    status = clink_runtime_create(&pair.runtime);
    if (status != CLINK_STATUS_OK) goto cleanup;

    /* 2. Publish a listener at the backend URL. */
    clink_listener_options listener_options = clink_listener_url(address);
    status = clink_listener_open_ex(pair.runtime, &listener_options, &pair.listener);
    if (status != CLINK_STATUS_OK) goto cleanup;

    /* 3. Open a client session to that same URL. */
    clink_session_info session_info = { sizeof(session_info), CLINK_ABI_VERSION };
    clink_session_options session_options = clink_session_url(address);
    status = clink_session_open(pair.runtime, &session_options, &pair.session, &session_info);
    if (status != CLINK_STATUS_OK) goto cleanup;

    /* 4. Client requests a channel inside the session. */
    clink_channel_info channel_info = { sizeof(channel_info), CLINK_ABI_VERSION };
    clink_connection_options connection_options = clink_connection_wait(5000);
    status = clink_connection_open_in_session(pair.session, &connection_options, &pair.client, &channel_info);
    if (status != CLINK_STATUS_OK) goto cleanup;

    /* 5. Listener accepts the peer endpoint for that channel. */
    clink_accept_result accept_result = { sizeof(accept_result), CLINK_ABI_VERSION };
    status = clink_listener_accept_wait(pair.listener, 5000, &pair.server, &accept_result);
    if (status != CLINK_STATUS_OK) goto cleanup;

    /* 6. Endpoints move opaque byte messages. */
    const uint8_t request[] = { 1, 2, 3 };
    status = clink_connection_send_wait(pair.client, request, sizeof(request), 5000);
    if (status != CLINK_STATUS_OK) goto cleanup;

    uint8_t buffer[16];
    uint64_t received = 0;
    status = clink_connection_recv_wait(pair.server, buffer, sizeof(buffer), &received, 5000);

cleanup:
    clink_demo_pair_close(&pair);
    return status;
}

C#

using EyeAuras.CLink;

var address = "file:///C:/temp/ea-clink/demo.ctl?maxPacketSize=65536";

using var serverRuntime = CLinkRuntime.Create();
using var listener = serverRuntime.OpenListener(new CLinkListenerOptions
{
    Address = address,
});

using var clientRuntime = CLinkRuntime.Create();
using var session = clientRuntime.OpenSession(new CLinkSessionOptions
{
    Address = address,
});

using var client = session.OpenConnection(new CLinkConnectionOptions
{
    TimeoutMs = 5000,
});

var acceptStatus = listener.AcceptWait(TimeSpan.FromSeconds(5), out var server, out var accept);
if (acceptStatus != CLinkStatus.Ok || server is null)
{
    throw new InvalidOperationException($"Accept failed: {acceptStatus}");
}

using (server)
{
    var sendStatus = client.SendWait(new byte[] { 1, 2, 3 }, TimeSpan.FromSeconds(5));
    if (sendStatus != CLinkStatus.Ok)
    {
        throw new InvalidOperationException($"Send failed: {sendStatus}");
    }

    var buffer = new byte[16];
    var receiveStatus = server.ReceiveWait(buffer, out var received, TimeSpan.FromSeconds(5));
    if (receiveStatus != CLinkStatus.Ok)
    {
        throw new InvalidOperationException($"Receive failed: {receiveStatus}");
    }
}

For same-process memory setup in C#, replace the file:// address with a managed descriptor block:

using var block = CLinkMemoryDescriptorBlock.Allocate();
var address = $"{block.Address}&maxPacketSize=65536";

Keep the block alive until after listener close.

Direct Endpoint Attach

Use this when the URL already names a concrete endpoint. Current examples are direct pipe://localhost/...?...protocol=clink-packet-v1 endpoints and returned shared-mem://... channel endpoints.

C

clink_status direct_endpoint_attach_example(clink_runtime_handle runtime)
{
    const char* endpoint_url =
        "pipe://localhost/EA_CLink/session/12?protocol=clink-packet-v1&maxPacketSize=65536";
    clink_utf8_span endpoint = clink_text(endpoint_url);
    clink_connection_handle connection = 0;
    const uint8_t payload[] = { 7, 8, 9 };
    clink_status status;

    /* Open the concrete endpoint URL directly. */
    status = clink_connection_open(runtime, &endpoint, &connection);
    if (status != CLINK_STATUS_OK) return status;

    /* Use it like any other CLink connection. */
    status = clink_connection_send_wait(connection, payload, sizeof(payload), 5000);

    (void) clink_connection_close(connection);
    return status;
}

C#

var endpointAddress = "pipe://localhost/EA_CLink/session/12?protocol=clink-packet-v1&maxPacketSize=65536";

var status = runtime.TryOpenConnection(endpointAddress, out var connection);
if (status != CLinkStatus.Ok || connection is null)
{
    throw new InvalidOperationException($"Open failed: {status}");
}

using (connection)
{
    status = connection.SendWait(new byte[] { 7, 8, 9 }, TimeSpan.FromSeconds(5));
    if (status != CLinkStatus.Ok)
    {
        throw new InvalidOperationException($"Send failed: {status}");
    }
}

shared-mem:// endpoint addresses are usually returned by an accepted channel. Treat an attached handle as an alias for one existing channel role; do not concurrently send or receive through two handles for the same role.

Optional Bootstrap

CLinkControl is one optional byte protocol that can run over any CLink endpoint. A product can use it to ask for one or more endpoint URLs, then open those URLs normally.

CLink does not require CLinkControl. Products may use command-line arguments, environment variables, registry values, files, named pipes, sockets, or their own byte protocol to exchange endpoint URLs.

C

clink_status optional_bootstrap_example(clink_runtime_handle runtime)
{
    const char* bootstrap_url =
        "pipe://localhost/EA_CLink/bootstrap?protocol=clink-packet-v1&maxPacketSize=65536";
    clink_utf8_span bootstrap_address = clink_text(bootstrap_url);
    clink_connection_handle bootstrap = 0;
    clink_connection_handle data = 0;
    clink_status status;

    /* 1. Open a known bootstrap endpoint. */
    status = clink_connection_open(runtime, &bootstrap_address, &bootstrap);
    if (status != CLINK_STATUS_OK) return status;

    /* 2. Product code exchanges bytes over bootstrap and receives a concrete URL. */
    const char* returned_endpoint_text = product_open_endpoint_over_bootstrap(bootstrap);
    clink_utf8_span returned_endpoint = clink_text(returned_endpoint_text);

    /* 3. Open the returned URL normally. */
    status = clink_connection_open(runtime, &returned_endpoint, &data);

    if (data) (void) clink_connection_close(data);
    if (bootstrap) (void) clink_connection_close(bootstrap);
    return status;
}

C#

var bootstrapAddress = "pipe://localhost/EA_CLink/bootstrap?protocol=clink-packet-v1&maxPacketSize=65536";

var status = runtime.TryOpenConnection(bootstrapAddress, out var bootstrap);
if (status != CLinkStatus.Ok || bootstrap is null)
{
    throw new InvalidOperationException($"Bootstrap open failed: {status}");
}

using (bootstrap)
{
    // Product code runs CLinkControl or another bootstrap protocol over bootstrap.
    IReadOnlyList<string> endpointUrls = ProductOpenEndpointOverBootstrap(bootstrap);

    status = runtime.TryOpenConnection(endpointUrls[0], out var data);
    if (status != CLinkStatus.Ok || data is null)
    {
        throw new InvalidOperationException($"Endpoint open failed: {status}");
    }

    using (data)
    {
        // Move profile-specific bytes here.
    }
}

The important rule is that the bootstrap result is still just a concrete URL. Once the URL is known, C and C# callers use the same normal open/send/receive APIs.

Closing Rules

  • Close connections before sessions, sessions before listeners, and listeners before runtimes when ownership is explicit.
  • A wait may return PeerClosed, Timeout, Busy, MessageTooLarge, or another clink_status / CLinkStatus; treat the status as the operation result.
  • maxPacketSize is enforced by the backend that accepted the URL. Oversized messages return MessageTooLarge.

No packages depend on EyeAuras.CLink.

.NET 8.0

Version Downloads Last updated
0.1.11 6 05/10/2026