Futhark
Automatic wrapping of C headers in Nim
Install / Use
/learn @PMunch/FutharkREADME

Have your eyes set on the perfect C library for your project? Can't find a wrapper for it in Nim? Look no further! Futhark aims to allow you to simply import C header files directly into Nim, and allow you to use them like you would from C without any manual intervention. It's still in a beta state, but it already wraps most header files without any rewrites or pre-processing and has successfully been used to wrap complex projects.
import futhark, strutils
# Remove the `stbi_` prefix since Nim doesn't struggle as much with collisions as C
proc renameCb(n: string, k: SymbolKind, p: string, overloading: var bool): string =
n.replace "stbi_", ""
# Tell futhark where to find the C libraries you will compile with, and what
# header files you wish to import.
importc:
path "./stb"
define STB_IMAGE_IMPLEMENTATION # This define is required by the STB library
renameCallback renameCb
rename FILE, CFile # Rename `FILE` that STB uses to `CFile` which is the Nim equivalent
"stb_image.h"
# Tell Nim how to compile against the library. If you have a dynamic library
# this would simply be a `--passL:"-l<library name>`
static:
writeFile("test.c", """
#define STB_IMAGE_IMPLEMENTATION
#include "./stb/stb_image.h"
""")
{.compile: "test.c".}
# Use the library just like you would in C!
var width, height, channels: cint
var image = load("futhark.png", width.addr, height.addr, channels.addr, STBI_default.cint)
if image.isNil:
echo "Error in loading the image"
quit 1
echo "Loaded image with a width of ", width, ", a height of ", height, " and ", channels, " channels"
image_free(image)
So are all C wrappers now obsolete?
Not quite. Futhark only tells you what the C headers define and allows you to use them. This means that the interface is still very C-like. A lot of great Nim wrappers will take a C library and wrap it into something that is a little more simple to use from Nim land. But Futhark can definitely be used to help with wrapping C libraries. Since it reads the C files directly you are guaranteed that all the types match up with their C counterparts, no matter what platform you're on, or what defines you want to pass. This is a huge benefit over hand-wrapped code. Futhark and Øpir will also cache their results, so after the initial compilation it's just as fast to use as it simply grabs the pre-generated Nim file from the cache. Both files could of course also be edited or included as-is in a project if you want users to not have to run Øpir or Futhark themselves.
How does it work?
Basically Futhark comprises of two parts, a helper program called Øpir (or
opir just to ensure that it works everywhere) and a module called futhark
that exposes a importc macro. Øpir is compiled with libclang and uses Clang
to parse and understand the C files, it then creates a big JSON output of
everything that is defined in the headers with Nim friendly types. The macro
then reads this file and applies any overrides to types and names before it
generates all the Nim definitions.
Basic usage
A lot of people coming from other wrapping tools start out a bit confused about
Futhark. With other tools it is common to wrap things into a Nim module first,
then import that module into your program. This is not the way Futhark is
supposed to be used. With Futhark you handle your C imports in the importc
block and just import the module in which you have that block. This is to ensure
that all defines, configurations, and platform specific things will match up
between your code and the C code. It is however possible to use Futhark to
generate a wrapper, this is detailed in the "Shipping wrappers" section of this
README.
The four main things you need to know to use Futhark is sysPath, path,
compilerArgs, and normal imports (the "stb_image.h" part in the above
example).
sysPathdenotes system paths, these will be passed to Øpir to make sure Clang knows where to find all the definitions. This can also be passed with-d:sysPath:<path 1>:<path 2>if you want to automatically generate these. By default Futhark tries to find thesysPathautomatically and you don't need to specify this yourself.pathdenotes library paths, these will also be passed to Øpir, but anything found in these files which is used by anything in the explicitly imported files will be wrapped by Futhark as well.compilerArgsspecifies additional flags that should be passed to Clang when parsing the C headers.- Files listed in quotes in the importc are equivalent to
#include "file.h"in C. Futhark will generate all definitions in these files, and iffile.himports more files found in any of the paths passed in bypaththese files will also be imported.
Note: The difference between sysPath and path is simply about how Futhark
handles definitions found in these paths. sysPath are paths which are fed to
Øpir and Clang in order to make Clang able to read all the types. path are
the paths Futhark takes into account when generating definitions. This
difference exists to make sure Futhark doesn't import all kinds of low-level
system stuff which is already available in Nim. A subpath of sysPath can be
passed in with path without trouble. So sysPath "/usr/include" followed by
path "/usr/include/X11" is fine and Futhark will only generate code for the
explicitly mentioned files, and any files it requires from /usr/include/X11.
Hard names and overrides
Nim, unlike C, is case and underscore insensitive and doesn't allow you to have
identifiers starting or ending with _, or identifiers that have more than one
consecutive _ in them. Nim also has a set of reserved keywords like proc,
addr, and type which would be inconvenient to have as names. Because of
this Futhark will rename these according to some fairly simple rules.
| Name issue | Nim rename |
| ---------------- | --------------------------------------------------- |
| struct type | struct_ prefix |
| union type | union_ prefix |
| enum type | enum_ prefix |
| _ prefix | internal_ prefix |
| __ prefix | compiler_ prefix |
| _ postfix | _private postfix |
| __ in name | All consecutive underscores collapsed into one |
| Reserved keyword | Append kind to name, proc, const, struct etc. |
Apart from this the name is kept as it appears in the C sources, but note that
because of Nims style insensitivity you can still call some_proc as someProc
without any issues. This renaming scheme, along with Nims style-insensitivity,
does however mean that some identifiers might collide. In this case the name
will further have the kind appended, and if it still collides it will append
the hash of the original identifier. This shouldn't happen often in real
projects and exists mostly to create a foolproof renaming scheme. Note that
struct and union types also get a prefix, this is normally resolved
automatically by C typedef-ing the struct struct_name to struct_name_t, but
in case you need to use a struct struct_name type just keep in mind that in
Nim it will be struct_struct_name.
If you want to rename an object or a field you can use the rename directive.
Simply put rename <from>, <to> along with your other options. <from> can be
either just an object name (before any other renaming) as a string or ident, or
a field in the format <object>.<field> both the original C names either as
two identifiers, or the whole thing as a single string. <to> is always a
single identifier and is the new name.
If you want to implement more complex renaming you can use the renameCallback
directive and pass in a callback function that takes the original name, a
string denoting what kind of identifier this is, and an optional string
denoting which object or procedure this identifier occurs in, and which returns
a new string. This callback will be inserted into the renaming logic and will
be called on the original C identifier before all the other rules are applied.
See the Deeper control > Rename callback section for more information.
Redefining types
C tends to use a lot of void pointers, pointers to characters, and pointers to
a single element which is supposed to be a collection of said element. In Nim
we like to be a bit more strict about our types. For this you can use the
retype directive. It takes the form retype <object>.<field>, <Nim type> so
for example to retype the C array type defined as some_element* some_field to
an indexable type in Nim you can use
retype some_object.some_field, ptr UncheckedArray[some_element]. The names
for the object and field are both their renamed Nim identifiers.
If you need to redefine an entire object, instead of just specific fields
Futhark by default also guards every type and procedure definiton in simple
when declared(SomeType) statements so that if you want to override a
definition you can simply define your type before the importc macro
invocation and Futhark won't override your definition. It is up to you however
to ensure that this type actually matches in size and layout with the original
C type.
Compatibility features and readability
Futhark by default tries to ensure the highest amount of compatibility with
pre-wrapped libraries (e.g. the posix standard library module) and other user
code. Because of this the output which Futhark generates isn't very pretty,
being littered with when defined statements and weird numbered
identifiers for renaming things. These features are intended to make Futhark
easier to use in a mostly automatic fashion, but you might not
