cimport

Procs

proc cDebug() {...}{.compileTime, raises: [], tags: [].}
Enable debug messages and display the generated Nim code
proc cDisableCaching() {...}{.compileTime, raises: [], tags: [].}

Disable caching of generated Nim code - useful during wrapper development

If files included by header being processed by cImport() change and affect the generated content, they will be ignored and the cached value will continue to be used . Use cDisableCaching() to avoid this scenario during development.

nim -f can also be used to flush the cached content.

proc cSearchPath(path: string): string {...}{.compileTime, raises: [], tags: [ReadDirEffect].}

Get full path to file or directory path in search path configured using cAddSearchDir() and cAddStdDir().

This can be used to locate files or directories that can be passed onto cCompile(), cIncludeDir() and cImport().

proc cAddSearchDir(dir: string) {...}{.compileTime, raises: [], tags: [].}
Add directory dir to the search path used in calls to cSearchPath().

Example:

import nimterop/paths, os
static:
  cAddSearchDir testsIncludeDir()
  doAssert cSearchPath("test.h").fileExists
proc cAddStdDir(mode = "c") {...}{.compileTime, raises: [ValueError, OSError, Exception,
    IOError, KeyError], tags: [ExecIOEffect, ReadIOEffect, RootEffect, WriteIOEffect,
                            ReadEnvEffect, ReadDirEffect].}
Add the standard c [default] or cpp include paths to search path used in calls to cSearchPath().

Example:

import os
static:
  cAddStdDir()
  doAssert cSearchPath("math.h").fileExists
proc cSkipSymbol(skips: seq[string]) {...}{.compileTime, raises: [], tags: [].}

Similar to cOverride(), this macro allows filtering out symbols not of interest from the generated output.

cSkipSymbol() only affects calls to cImport() that follow it.

Example:

static: cSkipSymbol @["proc1", "Type2"]

Macros

macro cDefine(name: static[string]; val: static[string] = ""): untyped

#define an identifer that is forwarded to the C/C++ preprocessor if called within cImport() or c2nImport() as well as to the C/C++ compiler during Nim compilation using {.passC: "-DXXX".}

This needs to be called before cImport() to take effect.

macro cDefine(values: static seq[string]): untyped

#define multiple identifers that are forwarded to the C/C++ preprocessor if called within cImport() or c2nImport() as well as to the C/C++ compiler during Nim compilation using {.passC: "-DXXX".}

This needs to be called before cImport() to take effect.

macro cIncludeDir(dirs: static seq[string]; exclude: static[bool] = false): untyped

Add include directories that are forwarded to the C/C++ preprocessor if called within cImport() or c2nImport() as well as to the C/C++ compiler during Nim compilation using {.passC: "-IXXX".}.

Set exclude = true if the contents of these include directories should not be included in the wrapped output.

This needs to be called before cImport() to take effect.

macro cIncludeDir(dir: static[string]; exclude: static[bool] = false): untyped

Add an include directory that is forwarded to the C/C++ preprocessor if called within cImport() or c2nImport() as well as to the C/C++ compiler during Nim compilation using {.passC: "-IXXX".}.

Set exclude = true if the contents of this include directory should not be included in the wrapped output.

This needs to be called before cImport() to take effect.

macro cExclude(paths: static seq[string]): untyped

Exclude specified paths - files or directories from the wrapped output

Full path to file or directory is required.

macro cExclude(path: static string): untyped

Exclude specified path - file or directory from the wrapped output.

Full path to file or directory is required.

macro cPassC(value: static string): untyped

Create a {.passC.} entry that gets forwarded to the C/C++ compiler during Nim compilation.

cPassC() needs to be called before cImport() to take effect and gets consumed and reset so as not to impact subsequent cImport() calls.

macro cPassL(value: static string): untyped

Create a {.passL.} entry that gets forwarded to the C/C++ compiler during Nim compilation.

cPassL() needs to be called before cImport() to take effect and gets consumed and reset so as not to impact subsequent cImport() calls.

macro cCompile(path: static string; mode: static[string] = "c";
              exclude: static[string] = ""): untyped

Compile and link C/C++ implementation into resulting binary using {.compile.}

path can be a specific file or contain * wildcard for filename:

cCompile("file.c")
cCompile("path/to/*.c")

mode recursively searches for code files in path.

c searches for *.c whereas cpp searches for *.C *.cpp *.c++ *.cc *.cxx

cCompile("path/to/dir", "cpp")

exclude can be used to exclude files by partial string match. Comma separated to specify multiple exclude strings

cCompile("path/to/dir", exclude="test2.c")

cCompile() needs to be called before cImport() to take effect and gets consumed and reset so as not to impact subsequent cImport() calls.

macro renderPragma(): untyped

All cDefine(), cIncludeDir(), cCompile(), cPassC() and cPassL() content typically gets forwarded via cImport() to the generated wrapper to be rendered as part of the output so as to enable standalone wrappers. If cImport() is not being used for some reason, renderPragma() can create these pragmas in the nimterop wrapper itself. A good example is using getHeader() without calling cImport().

c2nImport() already uses this macro so there's no need to use it when typically wrapping headers.

macro cOverride(body): untyped

When the wrapper code generated by nimterop is missing certain symbols or not accurate, it may be required to hand wrap them. Define them in a cOverride() macro block so that Nimterop uses these definitions instead.

For example:

int svGetCallerInfo(const char** fileName, int *lineNumber);

This might map to:

proc svGetCallerInfo(fileName: ptr cstring; lineNumber: var cint)

Whereas it might mean:

cOverride:
  proc svGetCallerInfo(fileName: var cstring; lineNumber: var cint)

Using the cOverride() block, nimterop can be instructed to use this definition of svGetCallerInfo() instead. This works for procs, consts and types.

cOverride() only affects the next cImport() call. This is because any recognized symbols get overridden in place and any remaining symbols get added to the top. If reused, the next cImport() would add those symbols again leading to redefinition errors.

macro cPlugin(body): untyped
When cOverride() and cSkipSymbol() are not adequate, the cPlugin() macro can be used to customize the generated Nim output. The following callbacks are available at this time.
proc onSymbol(sym: var Symbol) {.exportc, dynlib.}

onSymbol() can be used to handle symbol name modifications required due to invalid characters in identifiers or to rename symbols that would clash due to Nim's style insensitivity. The symbol name and type is provided to the callback and the name can be modified.

While cPlugin can easily remove leading/trailing _ or prefixes and suffixes like SDL_, passing --prefix or --suffix flags to cImport in the flags parameter is much easier. However, these flags will only be considered when no cPlugin is specified.

Returning a blank name will result in the symbol being skipped. This will fail for nskParam and nskField since the generated Nim code will be wrong.

Symbol types can be any of the following:

  • nskConst for constants
  • nskType for type identifiers, including primitive
  • nskParam for param names
  • nskField for struct field names
  • nskEnumField for enum (field) names, though they are in the global namespace as nskConst
  • nskProc - for proc names

macros and nimterop/plugins are implicitly imported to provide access to standard plugin facilities.

cPlugin() only affects calls to cImport() that follow it.

Example:

cPlugin:
  import strutils

  # Strip leading and trailing underscores
  proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
    sym.name = sym.name.strip(chars={'_'})

Example:

cPlugin:
  import strutils

  # Strip prefix from procs
  proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
    if sym.kind == nskProc and sym.name.contains("SDL_"):
      sym.name = sym.name.replace("SDL_", "")
macro cPluginPath(path: static[string]): untyped

Rather than embedding the cPlugin() code within the wrapper, it might be preferable to have it stored in a separate source file. This allows for reuse across multiple wrappers when applicable.

The cPluginPath() macro enables this functionality - specify the path to the plugin file and it will be consumed in the same way as cPlugin().

path is relative to the current dir and not necessarily relative to the location of the wrapper file. Use currentSourcePath to specify a path relative to the wrapper file.

Unlike cPlugin(), this macro also does not implicitly import any other modules since the standalone plugin file will need explicit imports for nim check and suggestions to work. import nimterop/plugin is required for all plugins.

macro cImport(filenames: static seq[string]; recurse: static bool = false;
             dynlib: static string = ""; mode: static string = "c";
             flags: static string = ""; nimFile: static string = ""): untyped

Import multiple headers in one shot

This macro is preferable over multiple individual cImport() calls, especially when the headers might #include the same headers and result in duplicate symbols.

macro cImport(filename: static string; recurse: static bool = false;
             dynlib: static string = ""; mode: static string = "c";
             flags: static string = ""; nimFile: static string = ""): untyped

Import all supported definitions from specified header file. Generated content is cached in nimcache until filename changes unless cDisableCaching() is set. nim -f can also be used to flush the cache.

recurse can be used to generate Nim wrappers from #include files referenced in filename. This is only done for files in the same directory as filename or in a directory added using cIncludeDir().

dynlib can be used to specify the Nim string to use to specify the dynamic library to load the imported symbols from. For example:

const
  dynpcre =
    when defined(Windows):
      when defined(cpu64):
        "pcre64.dll"
      else:
        "pcre32.dll"
    elif hostOS == "macosx":
      "libpcre(.3|.1|).dylib"
    else:
      "libpcre.so(.3|.1|)"

cImport("pcre.h", dynlib="dynpcre")

If dynlib is not specified, the C/C++ implementation files can be compiled in with cCompile(), or the {.passL.} pragma can be used to specify the static lib to link.

mode selects the preprocessor and tree-sitter parser to be used to process the header.

flags can be used to pass any other command line arguments to toast. A good example would be --prefix and --suffix which strip leading and trailing strings from identifiers, _ being quite common.

nimFile is the location where the generated wrapper should get written. By default, the generated wrapper is written to nimcache and included from there. nimFile makes it possible to write the wrapper to a predetermined location which can then be directly imported into the main application and checked into source control if preferred. Importing the nimterop wrapper with nimFile specified still works per usual. If nimFile is not an absolute path, it is relative to the project path.

cImport() consumes and resets preceding cOverride() calls. cPlugin() is retained for the next cImport() call unless a new cPlugin() call is defined.

macro c2nImport(filename: static string; recurse: static bool = false;
               dynlib: static string = ""; mode: static string = "c";
               flags: static string = ""; nimFile: static string = ""): untyped

Import all supported definitions from specified header file using c2nim

Similar to cImport() but uses c2nim to generate the Nim wrapper instead of toast. Note that neither cOverride(), cSkipSymbol() nor cPlugin() have any impact on c2nim.

toast is only used to preprocess the header file and recurse if specified.

mode should be set to cpp for c2nim to wrap C++ headers.

flags can be used to pass other command line arguments to c2nim.

nimFile is the location where the generated wrapper should get written, similar to cImport().

nimterop does not depend on c2nim as a nimble dependency so it does not get installed automatically. Any wrapper or library that requires this proc needs to install c2nim with nimble install c2nim or add it as a dependency in its own .nimble file.