winim/dotnet

This module adds modern .NET support for Winim. It lets Nim load .NET libraries, create objects, call methods, access properties, and use common .NET features such as collections, events, and asynchronous operations. Full features require .NET 8+; lower versions may not support some features.

A typical program starts the .NET runtime, loads a library, creates an object, and closes the runtime when it is finished:

import winim/dotnet

block:
  dotnetStart()
  defer: dotnetClose()
  
  let core = load("System.Private.CoreLib")
  let builder = core.new("System.Text.StringBuilder")
  builder.Append("Hello from Nim")
  echo builder

Instance methods and properties can be accessed with dot syntax. Static methods and properties are accessed through a type from the same library:

import winim/dotnet

block:
  dotnetStart()
  defer: dotnetClose()
  
  let core = load("System.Private.CoreLib")
  let int32Type = core.getType("System.Int32")
  echo int32Type.Parse("12345")
  echo int32Type.MaxValue

The same interface can be used with .NET collections. Collections can be indexed, modified, and iterated:

import winim/dotnet

block:
  dotnetStart()
  defer: dotnetClose()
  
  let collections = load("System.Collections.NonGeneric")
  let items = collections.new("System.Collections.ArrayList")
  for item in ["one", "two", "three"]:
    items.Add(item)
  echo items[0]
  items[0] = "ONE"
  for item in items:
    echo item

The examples above show the basic calling style. For a larger walkthrough, see examples/dotnet/usage_demo.nim. Windows Forms and event handling are shown in examples/dotnet/simple_gui.nim, while asynchronous .NET calls are shown in examples/dotnet/async_download.nim.

.NET objects are cleaned up automatically. Use release when an object should be released before its surrounding scope ends, and call dotnetClose when the program is finished with .NET.

Imports

lean, objbase

Types

DotnetAssembly = ref object
  name*: string              ## Assembly name without `.dll`.
  path*: string              ## Resolved absolute assembly path.
A loaded assembly used for type lookup and construction.
DotnetClass = ref object
  typeName*: string          ## Managed type name.
  assemblyName*: string      ## Optional assembly identity.
A type proxy for constructors and static members.
DotnetComponentEntryPoint = proc (data: pointer; size: int32): int32 {.stdcall.}
Default signature for load and getMethod.
DotnetDelegateBodyBuilder = proc (expression: DotnetClass;
                                  parameters: seq[DotnetObject]): DotnetObject {.
    closure.}
Builds the expression body for a managed delegate. parameters are created from the delegate's Invoke signature.
DotnetDelegateCallback = proc (args: openArray[DotnetObject]): DotnetObject {.
    closure.}
Callback invoked by a managed delegate created with newDelegate. Arguments are valid managed object wrappers for the duration of the call. Return a managed value compatible with the delegate return type, or nil for void and nullable/reference return types.
DotnetError = object of CatchableError
  code*: int32               ## Hostfxr or Win32 error code.
Raised when hosting or a managed bridge operation fails.
DotnetEventHandler = proc (sender, args: DotnetObject) {.closure.}
Callback invoked by pollEvents for an event registered with on.
DotnetEventToken = object
Opaque identity for one managed event registration. Use it on the thread that called on.
DotnetMethod = object
  address*: pointer          ## Native entry-point address.

A native address for one managed static method.

Use asProc only with a Nim proc type whose calling convention and parameter layout exactly match the managed delegate type used during lookup. The address is valid while the associated host remains open.

DotnetObject = ref DotnetObjectData
An opaque CoreCLR object or boxed-value handle.
DotnetRuntimeInfo = object
  runtimeConfigPath*: string
  runtimeConfigGenerated*: bool
  frameworkName*: string
  frameworkVersion*: string
  hostfxrPath*: string
  dotnetRoot*: string
Read-only information about the current thread's .NET host.
DotnetTaskCanceledError = object of DotnetError
Raised when an awaited managed Task or ValueTask was canceled.
DotnetValue = object
  case kind*: DotnetValueKind
  of Null:
    nil
  of Bool:
    boolValue*: bool         ## Value stored for `Bool`.
  of Int64:
    int64Value*: int64       ## Value stored for `Int64`.
  of UInt64:
    uint64Value*: uint64     ## Value stored for `UInt64`.
  of Float64:
    float64Value*: float64   ## Value stored for `Float64`.
  of String:
    stringValue*: string     ## Value stored for `String`.
  of Object:
    objectValue*: DotnetObject ## Handle stored for `Object`.
Primitive or object value passed to a managed constructor, property, or method. Create complex values through managed APIs.
DotnetValueKind = enum
  Null,                     ## A managed null reference.
  Bool,                     ## A Boolean value.
  Int64,                    ## A signed integer value.
  UInt64,                   ## An unsigned integer value.
  Float64,                  ## A floating-point value.
  String,                   ## A UTF-8 Nim string converted to String.
  Object                     ## An existing managed object or boxed value.

Consts

UnmanagedCallersOnly = "*"
Pass as delegateType to load or getMethod to select UnmanagedCallersOnly

Procs

proc `$`(self: DotnetObject): string {....raises: [DotnetError, ValueError,
    Exception], tags: [RootEffect], forbids: [].}
Returns the managed object's ToString() value; nil objects become "null".
proc `[]`(self: DotnetObject; args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Gets a value through a managed indexer with arbitrary arguments.
proc `[]`(self: DotnetObject; index: SomeOrdinal): DotnetObject {.discardable.}
Gets a value through a managed ordinal indexer, such as items[0].
proc `[]=`(self: DotnetObject; args: varargs[DotnetValue, toDotnetValue]) {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Sets a value through a managed indexer with arbitrary arguments.
proc `[]=`[T](self: DotnetObject; index: SomeOrdinal; value: T) {.discardable.}
Sets a value through a managed ordinal indexer, such as items[0] = value.
proc asProc[T](self: DotnetMethod): T

Casts the native address to a caller-supplied Nim proc type.

T must use the calling convention and parameter/return layout declared by the managed delegate passed to load or getMethod, or by the unmanaged method's ABI. This cast performs no signature validation.

proc call(self: DotnetMethod): int32 {.inline, ...raises: [DotnetError, Exception],
                                       tags: [RootEffect], forbids: [].}
Calls a default-signature method with no data.
proc call(self: DotnetMethod; data: openArray[byte]): int32 {.
    ...raises: [DotnetError, Exception], tags: [RootEffect], forbids: [].}
Calls a default-signature method with a byte buffer.
proc call(self: DotnetMethod; data: pointer; size: int32): int32 {.
    ...raises: [DotnetError, Exception], tags: [RootEffect], forbids: [].}
Calls a default-signature method with the given buffer.
proc call(self: DotnetMethod; data: string): int32 {.
    ...raises: [DotnetError, Exception], tags: [RootEffect], forbids: [].}
Calls a default-signature method with the string bytes (no trailing NUL).
proc call[T](self: DotnetMethod; data: T): int32
Calls a default-signature method with a copy of data.
proc call[T](self: DotnetMethod; data: var T): int32
Calls a default-signature method with the in-memory bytes of data.
proc dotnetClose() {....raises: [DotnetError, Exception], tags: [RootEffect],
                     forbids: [].}
Closes the current thread's implicit .NET host.
proc dotnetGet(self: DotnetObject; name: string): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Gets an instance property or field by name.
proc dotnetGet(typ: DotnetClass; name: string): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Gets a static property or field from a managed type proxy.
proc dotnetInvoke(self: DotnetObject; name: string;
                  args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Invokes an instance method by name and returns its result.
proc dotnetInvoke(typ: DotnetClass; name: string;
                  args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Invokes a static method on a managed type proxy.
proc dotnetRuntimeInfo(): DotnetRuntimeInfo {....raises: [DotnetError], tags: [],
    forbids: [].}
Returns read-only information about the current thread's .NET host.
proc dotnetSet[T](self: DotnetObject; name: string; value: T)
Sets an instance property or field by name.
proc dotnetSet[T](typ: DotnetClass; name: string; value: T)
Sets a static property or field on a managed type proxy.
proc dotnetStart(runtimeConfigPath = ""; hostfxrPath = ""; dotnetRoot = "";
                 frameworkName = "Microsoft.NETCore.App"; frameworkVersion = "") {....raises: [
    ValueError, OSError, DotnetError, Exception, IOError, CatchableError], tags: [
    ReadIOEffect, ReadDirEffect, ReadEnvEffect, RootEffect, WriteDirEffect,
    WriteIOEffect], forbids: [].}
Starts or reuses the current thread's implicit .NET host.
proc enumValue(typeName, value: string): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Creates a boxed enum value through the current thread's host.
proc getManagedType(assemblyQualifiedName: string): DotnetObject {.
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [ReadDirEffect, RootEffect], forbids: [].}
Returns a managed System.Type through the current thread's host.
proc getMethod(typeName, methodName: string; delegateType = ""): DotnetMethod {.
    discardable, ...raises: [DotnetError, Exception], tags: [RootEffect],
    forbids: [].}

Gets a static managed method through the current thread's host.

delegateType follows the rules of load; pass UnmanagedCallersOnly or "*" for a method marked UnmanagedCallersOnly.

proc getProperty(self: DotnetObject; name: string): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Explicit API alias for dotnetGet on an instance object.
proc getProperty(typ: DotnetClass; name: string): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Explicit API alias for dotnetGet on a static type proxy.
proc getType(assembly: DotnetAssembly; typeName: string): DotnetClass {.
    ...raises: [DotnetError], tags: [], forbids: [].}
Creates a type proxy for typeName; lookup is deferred until use.
proc invoke(self: DotnetObject; name: string;
            args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Explicit API alias for dotnetInvoke on an instance object.
proc invoke(typ: DotnetClass; name: string;
            args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Explicit API alias for dotnetInvoke on a static type proxy.
proc load(assemblyName: string): DotnetAssembly {.discardable,
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [ReadDirEffect, RootEffect], forbids: [].}
Loads a framework or file assembly through the current thread's host.
proc load(assemblyPath, typeName, methodName: string; delegateType = ""): DotnetMethod {.
    discardable, ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [RootEffect], forbids: [].}

Loads a static managed method through the current thread's host.

delegateType is a managed delegate type name for non-default signatures. Pass UnmanagedCallersOnly or "*" for a method marked UnmanagedCallersOnly; cast the returned address with asProc[T] using the exact ABI.

proc loadAssembly(assemblyBytes: openArray[byte];
                  symbolsBytes: openArray[byte] = []) {.
    ...raises: [DotnetError, Exception], tags: [RootEffect], forbids: [].}
Loads an assembly from memory through the current thread's host.
proc loadAssembly(assemblyPath: string) {.
    ...raises: [DotnetError, Exception, ValueError, OSError], tags: [RootEffect],
    forbids: [].}
Loads an assembly through the current thread's host.
proc loadClass(typeName: string; assemblyName = ""): DotnetClass {.
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [ReadDirEffect, RootEffect], forbids: [].}
Creates a type proxy through the current thread's host.
proc new(assembly: DotnetAssembly; typeName: string;
         args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.discardable,
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Creates an object from a type in assembly.
proc new(typ: DotnetClass; args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Convenience alias for newObject(typ, args).
proc newClass(typeName: string; assemblyName = ""): DotnetClass {.
    ...raises: [DotnetError], tags: [], forbids: [].}
Creates a type proxy through the current thread's host.
proc newDelegate(delegateType: DotnetObject;
                 bodyBuilder: DotnetDelegateBodyBuilder): DotnetObject {.
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [RootEffect, ReadDirEffect], forbids: [].}
Creates a managed delegate by building its body from the delegate signature. The builder receives the expression type proxy and the parameter expressions created for Invoke.
proc newDelegate(delegateType: DotnetObject; callback: DotnetDelegateCallback): DotnetObject {.
    ...raises: [DotnetError, Exception, ValueError],
    tags: [RootEffect, ReadDirEffect], forbids: [].}

Creates a managed delegate backed by a Nim callback bridge.

Delegate parameters are boxed as managed objects and passed to the callback as temporary DotnetObject wrappers. The callback result is unboxed or cast to the delegate's declared return type. Byref, pointer, and byref-like parameters and return types are not supported. The callback must run on the host's owning thread.

proc newDelegate(delegateType: DotnetObject;
                 parameters: openArray[DotnetObject]; body: DotnetObject): DotnetObject {.
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [RootEffect, ReadDirEffect], forbids: [].}

Compiles a managed expression body into any compatible delegate type.

delegateType must be a managed System.Type representing a delegate, parameters must contain the corresponding managed parameter expressions, and body is the expression returned by the delegate. This is a low-level managed expression API; it does not automatically marshal an arbitrary Nim proc or closure into a .NET delegate.

proc newManagedArray(elementType: DotnetObject; values: openArray[DotnetObject]): DotnetObject {.
    ...raises: [DotnetError, Exception, ValueError, OSError],
    tags: [ReadDirEffect, RootEffect], forbids: [].}
Creates a managed array through the current thread's host.
proc newObject(typ: DotnetClass; args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Creates an instance represented by typ; arguments use managed overload resolution.
proc newObject(typeName: string; args: varargs[DotnetValue, toDotnetValue]): DotnetObject {.
    discardable, ...raises: [DotnetError, ValueError, Exception],
    tags: [RootEffect], forbids: [].}
Creates a managed object through the current thread's host.
proc off(self: DotnetObject; eventName: string): int {.discardable,
    ...raises: [DotnetError, KeyError, Exception], tags: [RootEffect], forbids: [].}
Removes all case-insensitive matching registrations on the registering thread and returns the count.
proc off(token: DotnetEventToken): bool {.discardable,
    ...raises: [KeyError, DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Removes exactly the registration represented by token. Returns false when it was already removed or is used on another thread.
proc on(self: DotnetObject; eventName: string; handler: DotnetEventHandler): DotnetEventToken {.
    discardable, ...raises: [DotnetError, Exception, CatchableError],
    tags: [RootEffect], forbids: [].}
Registers one Nim callback and returns its opaque removal token. Invoke pending callbacks on the registering thread with pollEvents.
proc pollEvents(): int {.discardable, ...raises: [DotnetError, Exception,
    ValueError, KeyError], tags: [RootEffect], forbids: [].}
Executes queued handlers for the current thread's host.
proc release(self: DotnetObject) {....raises: [Exception], tags: [RootEffect],
                                   forbids: [].}
Releases the managed handle immediately; nil and released objects are ignored.
proc setProperty[T](self: DotnetObject; name: string; value: T)
Explicit API alias for dotnetSet on an instance object.
proc setProperty[T](typ: DotnetClass; name: string; value: T)
Explicit API alias for dotnetSet on a static type proxy.
proc toBool(self: DotnetObject): bool {....raises: [DotnetError, ValueError,
    Exception], tags: [RootEffect], forbids: [].}
Converts a managed value to Nim bool.
proc toDotnetValue(x: bool): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim Boolean to a managed Boolean value.
proc toDotnetValue(x: char): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim character to a one-character managed String value.
proc toDotnetValue(x: cstring): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a C string to a managed String value.
proc toDotnetValue(x: DotnetObject): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a managed object handle; nil handles become managed null.
proc toDotnetValue(x: DotnetValue): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Returns an already encoded DotnetValue unchanged.
proc toDotnetValue(x: float32): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 32-bit float to a managed double-compatible numeric value.
proc toDotnetValue(x: float64): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim float to a managed floating-point value.
proc toDotnetValue(x: int): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim signed integer to a signed managed numeric value.
proc toDotnetValue(x: int8): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts an 8-bit signed integer to a signed managed numeric value.
proc toDotnetValue(x: int16): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 16-bit signed integer to a signed managed numeric value.
proc toDotnetValue(x: int32): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 32-bit signed integer to a signed managed numeric value.
proc toDotnetValue(x: int64): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 64-bit signed integer to a signed managed numeric value.
proc toDotnetValue(x: string): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim UTF-8 string to a managed String value.
proc toDotnetValue(x: typeof(nil)): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts Nim nil to a managed null reference.
proc toDotnetValue(x: uint): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a Nim unsigned integer to an unsigned managed numeric value.
proc toDotnetValue(x: uint8): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts an 8-bit unsigned integer to an unsigned managed numeric value.
proc toDotnetValue(x: uint16): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 16-bit unsigned integer to an unsigned managed numeric value.
proc toDotnetValue(x: uint32): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 32-bit unsigned integer to an unsigned managed numeric value.
proc toDotnetValue(x: uint64): DotnetValue {.inline, ...raises: [], tags: [],
    forbids: [].}
Converts a 64-bit unsigned integer to an unsigned managed numeric value.
proc toFuture(task: DotnetObject; pollIntervalMs = 10): Future[DotnetObject] {.
    ...stackTrace: false,
    raises: [Exception, ValueError, DotnetError, DotnetTaskCanceledError],
    tags: [RootEffect, TimeEffect], forbids: [].}

Waits for a managed Task or ValueTask without blocking the Nim async dispatcher.

The returned object is the value of Task<T>.Result or ValueTask<T>.Result. A non-generic awaitable completes with a nil result. The awaitable and its host must remain alive until the returned Future completes.

proc toInt(self: DotnetObject): int {....raises: [DotnetError, ValueError,
    Exception], tags: [RootEffect], forbids: [].}
Converts a managed value to platform int.
proc toInt64(self: DotnetObject): int64 {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Converts a managed value to int64.

Iterators

iterator items(source: DotnetObject): DotnetObject {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Iterates the values of a managed collection.
iterator pairs(source: DotnetObject): tuple[index: int, value: DotnetObject] {....raises: [
    DotnetError, ValueError, Exception, Exception, ValueError, DotnetError,
    Exception, ValueError, DotnetError], tags: [RootEffect], forbids: [].}

Iterates a managed collection as (index, value) pairs.

Supports an IEnumerator, an IEnumerable/GetEnumerator pattern, and finally an indexable object with Count or Length and an indexer.

Converters

converter dotnetObjectToBool(self: DotnetObject): bool {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Converts a managed Boolean value to Nim bool in Boolean contexts. This is the implicit form of toBool; nil or non-Boolean managed values retain the same conversion errors as the explicit procedure.
converter dotnetObjectToString(self: DotnetObject): string {.
    ...raises: [DotnetError, ValueError, Exception], tags: [RootEffect],
    forbids: [].}
Converts a DotnetObject to its managed ToString() representation.

Macros

macro `.`(v: DotnetClass; name: untyped; vargs: varargs[untyped]): untyped
Shorthand for static methods, properties, fields, and constructors on a type proxy.
macro `.`(v: DotnetObject; name: untyped; vargs: varargs[untyped]): untyped
Shorthand for instance methods, properties, and fields. Member names are resolved at runtime; explicit helpers remain available for dynamic names.
macro `.=`(v: DotnetClass; name: untyped; vargs: varargs[untyped]): untyped
Shorthand for setting a static property or field.
macro `.=`(v: DotnetObject; name: untyped; vargs: varargs[untyped]): untyped
Shorthand for setting an instance property or field.
macro dotnetScript(x: untyped): untyped
Extends property assignment to indexed setters such as a.b(c, d) = e, which invokes the managed set_b method.