How to link and use your own C library in Crystal

It is very easy to use C libraries in Crystal, and the documentation gives you some information on how to do just that. But what if you want to write something in C, or already have written something in C, that you want to bind to your Crystal app?

And what if you want to pass in a Crystal function as a callback that you use in your C code?

Thats what we’re going to do!

We are going to use an algorithm as an example, the algorithm we’re using displays a result in two parts, one almost immediately and one when the computations are all done.

Crystal is not only going to call our C code, but but our C code will also execute code we’ve written in Crystal. Sounds complicated? Well, it’s easier than you think.

The algorithm itself is a calculation of planet movements called n-body algorithm, and it is written in C and found here:

So we are going to take that code, and change it so that instead of the C code printing the results (remember, they don’ appear at the same time so just returning them would not be the same) the C code gets passed a Crystal function that outputs it. It is also going to take an input number that we give it from Crystal.


Requirements:

  • Crystal
  • C compiler (i’m using GCC in the examples)

First lets create 2 files in a project folder:

  • nbody.cr (our Crystal program file)
  • functions.c (our C program)

Open the C file, functions.c:

It’s empty but lets copy the code from the website and paste it in. I’ve collapsed all the functions since the calculations themselves are not interesting here, what we are interested in is the “main()” function and I’ve highlited the lines we will change:

NB!!! We will change the “atoi(argv[1])” statement as well

OK so you see here that it takes the input from the command line, uses that as an input to do the calculations and it prints the results to the terminal/console.

The function signature.

We want to change it so it takes:

  • an Integer from our Crystal code
  • a callback function we give it and it executes when each calculation is done

We don’t want to name our function main since it is a library. Lets rename it to “nbody”. Second, we dont need to return an integer anymore, so lets return “void”. Then we want to receive two arguments:

  • int value — the value we pass it from Crystal to base the calculations on
  • void (*callback(double)) — the callback function (or a pointer to a callback function), that takes a double (C equvivalent of Float64), and in our case returns nothing

It should look like this:

Secondly, we need to change the next line since we’re getting the input passed directly as an “int”:

Then we change the next two parts, and instad of printing out the results we are going to pass the results to the callback so our Crystal code decides what to do with them.

Lastly we remove the the return 0 statement in the bottom since we don’t return anything from the function. The code should now look like this(I only show the last piece of code, you must of course leave the rest of the code for it to work).

Compile the C code.

The last step is to compile our C code, I use GCC and the I’ll explain quickly the flags we set to compile it:

gcc -Wall -O3 -march=native -c functions.c -o functions.o
  • -Wall (enables all the warnings from the compiler, and you want those)
  • -O3 (thats the letter “O”, sets the optimizations to maximum, you can set it to -O2 also but don’t leave it blank, your code will be very slow)
  • -march=native (enables special optimizations for the CPU you are compiling on)
  • -c (compile only, we don’t do any linking)
  • functions.c (tells the compiler what file we want to compile)
  • -o functions.o (gives it an output name)

There are a whole set of different kind of flags, so google this if you’re curious. There should now be a file called functions.o in the folder.

The Crystal code.

Next open our nbody.cr file and write the following code (you can leave the comments out, they are just for information)

I won’t repete the comments but I’ll explain what we’re doing:

@[Link(ldflags: “#{__DIR__}/functions.o”)]

Here give the linker information on where to find the library we’re going to use.

lib LibFunctions

The “lib” declaration groups C functions and types that belong to this library. Note that you can call it what you want, but it’s good practice to call the objects that binds to C library “Lib…”

fun nbody(value : Int32, f : Float64 -> Nil)

The “fun” declaration inside a “lib” binds to a C function. You need to make sure you have the same name as the function you had in the C library.

We also tell it that the function takes a value of Int32, and the part that looks most cryptic is a “Proc” declaration:

f : Float64 -> Nil

This just declares what type of function we will give it, a Proc that takes a Float64 as input and returns nothing. As you see in the comments there is an alternative way of declaring the Proc that is more readable if they are new to you.

A Proc is a pointer to a function

Thats why we can pass it to the C code, and the C code can invoke it.

callback = ->(x : Float64) { puts “Written in Crystal: #{x}” }

Here we create the Proc itself. As you see it adheres to the declaration we gave above. All it does is add some text and print out the result from the computations that it will get when called in the C function.

LibFunctions.nbody(50000000, callback)

Last we call the C function, pass it a number and the callback (Proc) we just made.

Compile and run.

The last thing to do is to compile our code in crystal and run the program:

To compile in Crystal you write the following

The “ — release” flag tells Crystal to turn on optimizations, and the “ — no-debug” flag just tells it to skip any symbolic debug info. We didn’t specify a output filname here so the name of the executable will be the same as our source file.

Next run the program and verify that it works:

Yeah!!! It works. And if you run the code yourself you will see that each line pops up ca 5 secs after eachother.


Conclusions.

We did it really step by step here, but if you look at the code you needed to write to do all this it was not many lines. Actually, in Crystal we only wrote 6 lines!! And there wasn’t many abstractions and hoops to jump through.

Now we did something rather complex here by passing a callback function from our Crystal code to our C code, and invoke it there, but if you know how to do this, you also know just how easy it is to call a single function that’s not taking any callbacks.

Have fun!