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;
IClientRuntimeas 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:
- retain source-runtime affinity;
- know each element's public type;
- construct the corresponding .NET wrapper;
- 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
IClientRuntimeor 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: