Skip to content
EN FR

C# runtime strategy at ABI level

Documentation status: architecture — see Maturity and evidence.

Purpose

This page explains how the .NET binding connects generated C# wrappers to the logiCells Runtime without exposing ABI packing details to application code.

The normal programming model relies on:

  • generated .NET classes;
  • IClientRuntime as the execution contract;
  • a local or remote runtime-client implementation.
flowchart TB
    APP[C# application] --> API[Generated logiCells classes]
    API --> CLIENT[IClientRuntime]
    CLIENT --> LOCAL[NativeAbiRuntime]
    CLIENT --> REMOTE[RpcRuntime]
    LOCAL --> ENGINE[logiCells Runtime]
    REMOTE --> ENGINE

Status of the examples

This page describes the intended .NET binding architecture. Names such as IClientRuntime, NativeAbiRuntime, RpcRuntime, ObjectRef, and illustrative generated wrapper types express the public binding model; they must be verified against the generated package shipped with a specific release before being treated as exact API symbols.

The native ABI execution subset certified by the current engine is documented separately in Marshalling and value kinds.

Explicit runtime injection

A public wrapper normally receives its runtime explicitly. This makes affinity visible and allows several runtimes to coexist in one process.

using rtl.Runtime;

using var runtime = new NativeAbiRuntime("logicells-runtime");
IClientRuntime client = runtime;

// The concrete type depends on the published ABI package.
using var worker = new AbiWorker(client);

AbiWorker is used by the binding documentation when it is part of the published surface. In an application package, always use the actual public name generated by that package.

Local and RPC

The same public wrapper can target a remote runtime:

using rtl.Runtime;

var http = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(30)
};

var transport = new HttpJsonTransport(http, endpoint);
IClientRuntime client = new RpcRuntime(transport);

Changing topology should not change the application object model. It does change operational behavior: latency, transport failures, authentication, retries, and availability.

ObjectRef and identity

ObjectRef identifies a Runtime object independently of transport. It carries the identity needed to create, invoke, and release an object without exposing its physical location.

Do not compare wrappers by managed reference to decide whether they represent the same Runtime object. Use identity published by the contract.

CallDescriptor and invocation

Generated classes use CallDescriptor internally. Application developers should use this layer directly only for advanced tooling or diagnostics.

Conceptually:

ObjectRef created = runtime.Create(typeId, constructorId, args);
var value = runtime.Invoke<int>(descriptor, created, args);
runtime.Release(created);

The normal surface remains ordinary generated members:

using var item = new PublishedItem(runtime);
item.Name = "example";
var state = item.State;

PublishedItem illustrates wrapper shape; the exact type name must come from the generated and certified API for the package in use.

Lifetime

Owning wrappers implement IDisposable.

using var item = new PublishedItem(runtime);

The rule is deterministic: an owning wrapper releases its Runtime reference during Dispose(). Do not rely on finalization for normal operation.

Returned objects should be documented as owned, borrowed, session-scoped, or parent-scoped.

Arrays and published objects

The vNext binding targets uniform support for one-dimensional arrays of primitives, strings, and published objects.

For object arrays the binding must:

  1. retain source-runtime affinity;
  2. know each element's public type;
  3. construct the corresponding .NET wrapper;
  4. apply declared ownership.

Async

Do not artificially convert a synchronous ABI operation into a public asynchronous method.

The binding should publish Async when the contract describes an operation that is genuinely asynchronous, job-based, streamed, or cancelable with documented semantics.

Client-side thread-pool offload remains an application decision.

Exceptions and diagnostics

The binding converts Runtime and transport failures into appropriate .NET exceptions. Diagnostics should preserve where possible:

  • operation identity;
  • Runtime identity;
  • RPC correlation identifier;
  • expected capability or contract version.

Startup compatibility

A compatible .NET assembly version alone does not prove Runtime compatibility. Production applications should perform explicit capability negotiation before creating stateful objects whenever the deployment model permits it.

Practices

  • depend on IClientRuntime or generated domain wrappers;
  • inject the runtime explicitly;
  • never move wrappers between runtimes;
  • use using / Dispose() for owning objects;
  • keep transport details out of domain types;
  • verify capabilities at startup;
  • consult the manifest or generated API reference for exact public names.

See also: