Claude
Skills
Sign in
Back

Nim C Interop

Included with Lifetime
$97 forever

Use when nim-C interoperability including calling C from Nim, wrapping C libraries, importc/exportc pragmas, header generation, FFI patterns, and building high-performance systems code integrating Nim with existing C codebases.

General

What this skill does


# Nim C Interop

## Introduction

Nim compiles to C, enabling seamless interoperability with C code and libraries.
This bi-directional integration allows Nim developers to leverage decades of C
libraries while exposing Nim code to C applications. Understanding C interop is
essential for systems programming and library wrapping.

The interop mechanism uses pragmas like importc, exportc, and header to declare
foreign functions and types. Nim's type system maps naturally to C, with explicit
control over memory layout and calling conventions. This enables zero-overhead
abstraction while maintaining C ABI compatibility.

This skill covers importing C functions and types, wrapping C libraries, header
file generation, memory layout control, callback handling, and patterns for safe
type-safe C interop in systems programming.

## Importing C Functions

Import C functions using pragmas to make them callable from Nim code.

```nim
# Basic C function import
proc printf(format: cstring): cint {.importc, varargs, header: "<stdio.h>".}

proc main() =
  printf("Hello from C!\n")
  printf("Number: %d\n", 42)

# C function with explicit name
proc c_sqrt(x: cdouble): cdouble {.importc: "sqrt", header: "<math.h>".}

proc useSqrt() =
  let result = c_sqrt(16.0)
  echo result  # 4.0

# Multiple headers
proc malloc(size: csize_t): pointer {.importc, header: "<stdlib.h>".}
proc free(p: pointer) {.importc, header: "<stdlib.h>".}

proc manualAlloc() =
  var p = malloc(100)
  # Use memory
  free(p)

# C types mapping
proc strlen(s: cstring): csize_t {.importc, header: "<string.h>".}
proc strcpy(dest, src: cstring): cstring {.importc, header: "<string.h>".}

# Variadic C functions
proc snprintf(buf: cstring, size: csize_t, format: cstring): cint
  {.importc, varargs, header: "<stdio.h>".}

proc formatString(): string =
  var buffer: array[256, char]
  discard snprintf(cstring(addr buffer), 256, "Value: %d", 42)
  result = $cstring(addr buffer)

# C macros as inline procs
proc EXIT_SUCCESS(): cint {.importc: "EXIT_SUCCESS", header: "<stdlib.h>".}
proc EXIT_FAILURE(): cint {.importc: "EXIT_FAILURE", header: "<stdlib.h>".}

# Function pointers
type
  CompareFunc = proc (a, b: pointer): cint {.cdecl.}

proc qsort(base: pointer, nmemb, size: csize_t, compar: CompareFunc)
  {.importc, header: "<stdlib.h>".}

proc compareInts(a, b: pointer): cint {.cdecl.} =
  let x = cast[ptr cint](a)[]
  let y = cast[ptr cint](b)[]
  return x - y

proc sortArray() =
  var arr = [5, 2, 8, 1, 9]
  qsort(addr arr[0], arr.len, sizeof(cint), compareInts)

# C struct access
type
  TimeSpec {.importc: "struct timespec", header: "<time.h>".} = object
    tv_sec: int
    tv_nsec: int

proc clock_gettime(clk_id: cint, tp: ptr TimeSpec): cint
  {.importc, header: "<time.h>".}

# Calling conventions
proc win_api_func(): cint {.stdcall, importc, dynlib: "kernel32.dll".}

# C++ name mangling
proc cpp_function(x: cint): cint
  {.importcpp, header: "<myheader.hpp>".}

# C library linking
{.passL: "-lm".}  # Link math library
proc cos(x: cdouble): cdouble {.importc, header: "<math.h>".}
```

Import pragmas enable calling C code with full type safety from Nim.

## Wrapping C Libraries

Create type-safe Nim wrappers around C libraries for idiomatic usage.

```nim
# Simple wrapper
type
  FileHandle = distinct cint

proc c_open(path: cstring, flags: cint): cint
  {.importc: "open", header: "<fcntl.h>".}

proc c_close(fd: cint): cint
  {.importc: "close", header: "<unistd.h>".}

proc openFile(path: string): FileHandle =
  let fd = c_open(cstring(path), 0)
  if fd < 0:
    raise newException(IOError, "Failed to open file")
  FileHandle(fd)

proc close(fh: FileHandle) =
  discard c_close(cint(fh))

# Wrapping libcurl
type
  Curl = distinct pointer

const CURLE_OK = 0

proc curl_easy_init(): Curl {.importc, header: "<curl/curl.h>".}
proc curl_easy_cleanup(curl: Curl) {.importc, header: "<curl/curl.h>".}
proc curl_easy_setopt(curl: Curl, option: cint, parameter: pointer): cint
  {.importc, varargs, header: "<curl/curl.h>".}

type
  CurlHandle = object
    handle: Curl

proc newCurl(): CurlHandle =
  result.handle = curl_easy_init()
  if result.handle.pointer == nil:
    raise newException(Exception, "Failed to initialize curl")

proc close(curl: CurlHandle) =
  curl_easy_cleanup(curl.handle)

proc setUrl(curl: CurlHandle, url: string) =
  discard curl_easy_setopt(curl.handle, 10002, cstring(url))

# RAII wrapper with destructor
type
  CurlSession = object
    curl: Curl

proc `=destroy`(session: var CurlSession) =
  if session.curl.pointer != nil:
    curl_easy_cleanup(session.curl)
    session.curl = Curl(nil)

proc newSession(): CurlSession =
  result.curl = curl_easy_init()

# Wrapping complex C API
type
  SqliteDb = distinct pointer
  SqliteStmt = distinct pointer

proc sqlite3_open(filename: cstring, db: ptr SqliteDb): cint
  {.importc, header: "<sqlite3.h>".}

proc sqlite3_close(db: SqliteDb): cint
  {.importc, header: "<sqlite3.h>".}

proc sqlite3_prepare_v2(
  db: SqliteDb, sql: cstring, nbyte: cint,
  stmt: ptr SqliteStmt, tail: ptr cstring
): cint {.importc, header: "<sqlite3.h>".}

type
  Database = object
    handle: SqliteDb

proc openDatabase(filename: string): Database =
  var db: SqliteDb
  let rc = sqlite3_open(cstring(filename), addr db)
  if rc != 0:
    raise newException(IOError, "Cannot open database")
  result.handle = db

proc `=destroy`(db: var Database) =
  if db.handle.pointer != nil:
    discard sqlite3_close(db.handle)

# C callback wrapping
type
  EventCallback = proc (data: pointer) {.cdecl.}

proc c_register_callback(cb: EventCallback, data: pointer)
  {.importc: "register_callback", header: "events.h".}

proc nimCallback(data: pointer) {.cdecl.} =
  echo "Callback triggered"

proc registerEvent() =
  c_register_callback(nimCallback, nil)
```

Wrappers provide Nim-idiomatic interfaces while preserving C library functionality.

## Exporting to C

Export Nim functions to C using exportc pragma for library creation.

```nim
# Basic export
proc add(a, b: cint): cint {.exportc.} =
  a + b

# Export with specific name
proc multiply(a, b: cint): cint {.exportc: "nim_multiply".} =
  a * b

# Export with dynlib
proc divide(a, b: cint): cint {.exportc, dynlib.} =
  if b == 0: return 0
  a div b

# Export complex types
type
  Point {.exportc.} = object
    x: cint
    y: cint

proc createPoint(x, y: cint): Point {.exportc.} =
  Point(x: x, y: y)

proc pointDistance(p1, p2: Point): cdouble {.exportc.} =
  let dx = (p2.x - p1.x).float
  let dy = (p2.y - p1.y).float
  sqrt(dx * dx + dy * dy)

# Export string operations
proc processString(input: cstring): cstring {.exportc.} =
  let s = $input
  result = cstring(s.toUpperAscii())

# Generating header file
{.emit: """/*TYPESECTION*/
typedef struct {
  int x;
  int y;
} Point;
""".}

proc generateHeader() {.exportc: "lib_init".} =
  echo "Library initialized"

# Export callbacks
type
  Callback = proc (value: cint) {.cdecl.}

proc registerCallback(cb: Callback) {.exportc.} =
  cb(42)

# Building shared library
# Compile with: nim c --app:lib --noMain mylib.nim

# Library initialization
proc NimMain() {.importc.}

proc libInit() {.exportc: "lib_init".} =
  NimMain()
  echo "Nim library initialized"

# Export with error handling
proc safeOperation(value: cint): cint {.exportc.} =
  try:
    if value < 0:
      raise newException(ValueError, "Negative value")
    result = value * 2
  except:
    result = -1
```

Exportc enables creating C-compatible libraries from Nim code.

## Memory Layout and Alignment

Control memory layout for C struct compatibility and performance.

```nim
# Packed structures
type
  PackedStruct {.packed.} = object
    a: uint8
    b: uint32
    c: uint8

echo sizeof(PackedStruct)  # 6 bytes (no padding)

# Aligned structures
type
  AlignedStruct {.align(16).} = object
    data: array[4, float32]

echo sizeof(AlignedStruct)  # Aligned to 16 bytes

# C struct layout
type
  CStruct {.importc, header: "myheader.h".} = object
    field1: cin

Related in General