cimport

This is the main nimterop import file to help with wrapping C/C++ source code.

Check out template.nim as a starting point for wrapping a new library. The template can be copied and trimmed down and modified as required. templite.nim is a shorter version for more experienced users.

All {.compileTime.} procs must be used in a compile time context, e.g. using:

static:
  cAddStdDir()

Procs

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.

Examples:

static :
  cSkipSymbol @["proc1", "Type2"]
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 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 was broken prior to 0.19.4 but can also be used to flush the cached content.

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

Examples:

import
  nimterop / paths, os

static :
  cAddSearchDir testsIncludeDir()
doAssert cSearchPath("test.h").existsFile
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()

Examples:

static :
  cAddStdDir()
import
  os

doAssert cSearchPath("math.h").existsFile

Macros

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.

Examples:

cPlugin:
  import
    strutils

  proc onSymbol*(sym: var Symbol) {...}{.exportc, dynlib.} =
    sym.name = sym.name.strip(chars = {'_'})

Examples:

cPlugin:
  import
    strutils

  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 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".}
macro cIncludeDir(dir: static string): 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".}.
macro cCompile(path: static string; mode = "c"; exclude = ""): untyped

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

path can be a specific file or contain wildcards:

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")
macro cImport(filenames: static seq[string]; recurse: static bool = false;
             dynlib: static string = ""; mode: static string = "c";
             flags: 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 = ""): 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 after Nim v0.19.4 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.

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 = ""): 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.

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.