Skip to content

Instantly share code, notes, and snippets.

@btbytes
Forked from zacharycarter/wclwn.md
Created October 2, 2018 10:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btbytes/45bdde0dc139613772d57be388500b62 to your computer and use it in GitHub Desktop.
Save btbytes/45bdde0dc139613772d57be388500b62 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.

The libraries we will be creating bindings to are :

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 and Mercurial 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 user manual for c2nim can be found here.


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.

Two pragmas which will be referred to frequently throughout this guide, are the cdecl and dynlib pragmas.


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 process to generate bindings to it. 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
Windows

Generate Bindings

Generating bindings for the soloud audio library couldn't be easier. The only tool we will need is c2nim.

Installing c2nim

Before we can use c2nim to translate C to Nim, we need to install it. c2nim is installed via the Nim package manager nimble, which ships with newer versions of Nim. You can verify you have nimble installed by issuing the following command :

$ nimble -version

If you receive a response similar to :

nimble v0.8.3 compiled at 2017-01-21 00:16:21

Then nimble is succesfully installed on your system. If this is not the case, please refer to the nimble installation guide for installation instructions.

After verifying your nimble installation you are ready to install c2nim. To do so, issue the following commands :

$ git clone https://github.com/nim-lang/c2nim.git
$ cd c2nim && nimble install

To verify you have successfully installed c2nim, issue the following command :

$ c2nim --version

If c2nim has been installed successfully, you should receive a response similar to :

0.9.12
Modifying soloud_c.h

Before running c2nim against the soloud_c.h header file, we need to make some modifications to it. Start by issuing the following commands :

$ cd soloud && cd include

Find the soloud_c.h header file inside the include directory, and open it in your favorite text editor. We are going to add a directive at the top of the file that c2nim will parse during its translation process.

Copy and paste the following into the top of the header file :

#ifdef C2NIM
#  dynlib solouddll
#  cdecl
#  if defined(windows)
#    define solouddll "soloud.dll"
#  elif defined(macosx)
#    define solouddll "libsoloud.dylib"
#  else
#    define solouddll "libsoloud.so"
#  endif
#endif

This directive instructs c2nim to annotate the Nim procs and types it produces with various pragmas. The contents of the dynlib pragma specifically, will vary depending on the operating system c2nim is run on.

This is all we need to do to prepare the header file for c2nim. As you will see later on in this guide, this is not always the case - more often than not, you will need to spend some time manipulating the contents of the header file in order for c2nim to be able to parse it.

The next step in generating our bindings is actually running c2nim against our modified version of soloud_c.h

Running c2nim

To run c2nim against our modified header file, issue the following commands :

$ c2nim soloud_c.h

You should see output similar to :

Users/bob/projects/soloud/include/soloud_c.h(502, 1) Warning: comment ' SOLOUD_C_H_INCLUDED' ignored [CommentXIgnored]
Hint: operation successful (503 lines compiled; 0 sec total; 1.504MiB; ) [SuccessX]

This warning can be safely ignored. If you re-examine the include directory's contents you'll notice a new file present : soloud_c.nim

I recommend opening this file and examining its contents. These are the auto-generated bindings c2nim has produced for us.

Binding compilation

The last step involved in the generation of our bindings, is to actually compile them. To do so issue the following command :

$ nim c soloud_c.nim

You will see quite a few compiler hints while the Nim compiler runs. Once it is finished you should see output similar to :

Hint:  [Link]
Hint: operation successful (11553 lines compiled; 0.871 sec total; 17.938MiB peekmem; Debug Build) [SuccessX]

Assuming that is the case, then congratulations! You have produced your first bindings to a C library with Nim!

Creating a Test

The final step in binding to any library is writing a simple test to ensure the bindings you have produced work. Let's write one really quickly. Go ahead and issue the following commands -

$ wget https://github.com/zacharycarter/soloud-nim/raw/master/examples/test.ogg
$ touch soloud_test.nim

Go ahead and open soloud_test.nim in your preferred text editor, and paste in the following snippet :

import soloud_c, times, os

var i, spin = 0

var sl : ptr Soloud

sl = Soloud_create()

discard Soloud_init(sl)

Soloud_setGlobalVolume(sl, 1)

var stream = WavStream_create()
discard WavStream_load(cast[ptr Wav](stream), "test.ogg")

let currentTime = epochTime()
let length = WavStream_getLength(stream)
discard Soloud_play(cast[ptr Soloud](sl), cast[ptr Wav](stream))

while epochTime() - currentTime <= length:
  sleep(100)

Soloud_deinit(sl)

Soloud_destroy(sl)

To run the test issue the following command :

$ nim c -r soloud_test.nim

This test will play an .ogg file and quit after its duration.


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