Skip to content

Instantly share code, notes, and snippets.

@dseeni
Forked from zacharycarter/wclwn.md
Created October 4, 2023 22:33
Show Gist options
  • Save dseeni/5ea41904233ad2500b0148327173a643 to your computer and use it in GitHub Desktop.
Save dseeni/5ea41904233ad2500b0148327173a643 to your computer and use it in GitHub Desktop.
Binding to C Libraries with Nim

Binding to C Libraries with NimNim


Table of Contents


Introduction

One of the many wonderful features of the Nim programming language is its interoperability with other programming languages and Foreign Function Interface. Summarized, Nim code can call code written in other programming languages like C or JavaScript, and vice versa. This means that binding to libraries written in other programming languages, like C, is possible with Nim.

This guide is an attempt to empower readers to create their own bindings to other libraries or code written in C. It will walk readers through a few examples demonstrating how bindings were created to some rather complex C libraries, and we'll also create brand new bindings to a C library which bindings don't yet exist for :

Note:

The author of this guide spends most of his spare time developing video games, and tools used for building them. Therefore the focus of the libraries presented in this guide will be game development related.

Note:

This guide assumes the reader has a certain amount of familiarity with the C programming language as well as the Nim programming language, however it does not require the reader be an expert in either language.

Familiarity with development tooling such as git or an equivalent source control management system is also assumed. Git will be used in all examples for cloning the projects we will be generating bindings to.


C2NIM

c2nim is a tool that will be referred to extensively throughout this guide. It is described on the project's github repository as being :

... a tool to translate ANSI C code to Nim. The output is human-readable Nim code that is meant to be tweaked by hand after the translation process.

This tool dramatically speeds up the process of generating bindings to C libraries. You simply provide it with the path to a C source or header file, and it will attempt to translate its contents from C to Nim.


The C Preprocessor

Many C programs contain macros and other constructs which the C preprocessor transforms prior to the program being compiled. These constructs can present difficulties in translation for c2nim and we will encounter several examples of this throughout the guide.

In certain cases simply ignoring or commenting out the offending C construct will be the appropriate course of action. In other situations, relying on the C preprocessor to transform the source into code c2nim can handle, will be the route we will take.

When the C preprocessor is used, it is important to remember that it is another step in the production of your bindings. If you wish to automate the process of creating and updating your bindings, you will need to account for any preprocessing that needs to take place. In other words, relying on the C preprocessor introduces further complexity into the process of producing Nim bindings to a C program.


Pragmas

Nim provides a number of built-in pragmas which are described as being :

... Nim's method to give the compiler additional information / commands without introducing a massive number of new keywords. Pragmas are processed on the fly during semantic checking. Pragmas are enclosed in the special {. and .} curly brackets. Pragmas are also often used as a first implementation to play with a language feature before a nicer syntax to access the feature becomes available.

It is not necessary to understand what every pragma mentioned in this guide does, or how pragmas work internally, but it is important to know that pragmas will be used and referred to. We will be also using c2nim to generate many of the pragmas our bindings will need in order to function.


Before We Begin

Building dependent libraries:

When binding to another library, it is often desirable, and necessary to have built the source code for the library you plan on binding to, and installed any libraries or other artifacts the build process produces. This guide will include instructions for building each library, but it is recommended that readers familiarize themselves with popular C|C++ build tools such as CMake, premake, and GENie.

This guide will NOT contain instructions for using specific OS package managers, or instructions for installing any libraries other than those directly related to the tutorials presented below.

Tests:

It can be extremely beneficial to examine existing tests | examples bundled with whatever library you are binding to. Tests can help you to ensure your bindings are functioning as expected, by giving you a baseline program to compare yours to. They can also help one to understand how a library is meant to be used, or where a certain API is defined. See this soloud test for an example.


Soloud

The soloud audio library bills itself as :

... an easy to use, free, portable c/c++ audio engine for games

This library was chosen as the first example, because it is a very straightforward project to generate bindings for. The library offers a C API which is the API we will be binding to.

Clone Project

The soloud project is available on github and can be cloned with the command :

git clone https://github.com/jarikomppa/soloud.git

Perform this command in whatever directory you would like to use as your workspace for this tutorial. This command will create a new folder named 'soloud' and will checkout the soloud project into it.

Explore Project

Now that the project has been cloned, its time to explore its structure and determine exactly what will need to be done in order to generate bindings to the library.

The soloud project is structured as follows :

Note:

A summary of the soloud project's directory structure, can be found on the project's website.

.
├── AUTHORS
├── CONTRIBUTING
├── LICENSE
├── README.md
├── build
├── demos
├── doc
├── glue
├── include
├── scripts
├── soloud.png
└── rc

The src directory contains the following subdirectories:

>.
├── audiosource
├── backend
├── c_api
├── core
├── filter
└── tools

If we take a look in the c_api folder, we'll find the following:

.
├── soloud.def
└── soloud_c.cpp

The file soloud_c.cpp contains the implementation for the soloud C API. Since we are going to bind to, and eventually be calling the C code, we need not concern ourselves with the details of the library's implementation.

What we are looking for instead, is the file where all of the types and methods the implementation source file references, are defined. Generally in most C|C++ projects these definitions and implementations are split into header or include, and source files. By convention, header | include files are typically placed in a folder named 'include', however this is not always the case.

The soloud project follows this convention, and you may have noticed that the directory structure of the project includes a folder named 'include'.

Inside the include folder resides a header file named soloud_c.h. Defined inside of this header file, are all of the types and methods that the C API exposes. The bindings we create will be Nim equivalents of these definitions.

Build project

The first and only prerequisite for building the soloud library, is installing the build tool it uses, GENie. You can either download precompiled binaries available via the project's github page, or build and install the project yourself.

To do so issue the following commands -

$ git clone https://github.com/bkaradzic/genie
$ cd genie
$ make

This will build GENie, and place an executable inside the bin/OS/ directory. Copy this executable to a location contained within your operating system's PATH environment variable, or add the genie/bin/OS folder to your system's PATH environment variable.

To test your GENie installation, you can issue the following command -

$ genie --version

And if GENie is installed correctly you will receive a response similar to -

GENie - Project generator tool <auto generated do not change>
https://github.com/bkaradzic/GENie

Assuming GENie was installed successfully, soloud is ready to be built. To do so on a POSIX | Windows operating system is rather trivial.

The first step in building soloud, is generating project files via GENie. If you encounter any troubles while generating project files with GENie, consult the manual by typing.

$ genie --help

Next, issue the following commands, depending on which operating system you are using.

OSX
$ cd soloud && cd build
$ genie xcode4
$ cd xcode4
$ open SoLoud.xcworkspace

Once Xcode is open, you'll want to switch to the SoloudDynamic project and add the AudioToolbox and CoreAudio frameworks to the project. After doing so the project should build without issue.

You'll need the libsoloud.dylib dynamic library file produced in the Products folder later in this tutorial. To prevent yourself from having to do so later, you can install the library to your /usr/local/lib directory with the following commands:

$ cp ../../lib/libsoloud.dylib /usr/local/lib 

The soloud audio library has now been built and installed on your machine.

Linux
$ cd soloud && cd build
$ genie gmake
$ cd gmake

Nuklear

Bearlib Terminal

SDL-GPU

Creating Nimian Bindings

FAQ

Credits


######Guide authored and maintained by Zachary Carter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment