Ruby and D Interop: Part 1
I am a big fan of Ruby and I am also a big fan of the less well known D programming language. Lately I had been wondering how hard it would be to get them both to work together. As we will find out - it is not actually all that hard to accomplish! This is the first entry in a three part series discussing Ruby and D interop.
Getting started
If you are following along at home this post assumes:
Interop options
There are a few immediate options available to us.
We do not always need the overhead and complexity of building against the devkit and I did not find the Fiddle documentation too useful.
For this project I opted to go with the RubyFFI method of interop. Add ffi to your gemfile or type gem install ffi
at the terminal to install for your Ruby installation.
D ABI
A really useful thing about D is it has ABI compatibility with C! This means that RubyFFI will have no issues interacting with functions defined in the shared libraries.
The simple case
To get started let us examine the simple case of making a function available to Ruby from D.
We have defined a simple function that we want to call from C code.
Type in dmd -shared simple.d
at the terminal to compile the library.
On the Ruby end we want to create the binding that tells RubyFFI how to interact with the D library.
If all goes well you should see the output of 3
in your terminal after running simple.rb
.
Windows notes:
- You will need to place the
export
keyword beforeextern(C)
or this will not work. - Rename the produced .exe to .dll or .so and adjust the binding code. It is not required by RubyFFI but keeps things consistent.
The interesting case
You may have noticed the above D code is identical to C code and that is not terribly interesting. Fear not for we can take full advantage of D!
Aha, now there we go, this code is much more in the D style.
So that is pretty awesome, we can leverage the D standard library, all of the standard functionality and RubyFFI is perfectly fine with the binary generated by it.
Unfortunately this will not work on Windows out of the box which is something we will get to shortly.
The D Runtime
So far our code on the D side has not performed any memory allocations. If we want to access this functionality amongst other things we will need to initialise the D Runtime.
Let us look at a perhaps contrived FizzBuzz program.
To compile this we now want to use the command dmd -shared -defaultlib=libphobos2.so fizz.d
.
We are now binding to two functions from the D Standard Library (Phobos) which is why we needed the -defaultlib switch. This gives us the rt_init and rt_term functions which are for initialising and terminating the D Runtime respectively.
If the rt_init function is not called by your Ruby script the called library code may hang or crash.
Unlike with C libraries thanks to the D Runtime we have a garbage collector working away in the background much like in Ruby. It is possible to disable the D garbage collector and do manual memory management if further performance is required.
What about Windows?
Add this snippet to the fizz.d
example from earlier. Compile with dmd -shared fizz.d -of="fizz.dll"
In the Windows case we do not need rt_init and rt_term. When we attach to the DLL the runtime is automatically initialised for us.
Conclusion
Phew! That was a lot to go through. Hopefully by this point you have a good idea how to build basic RubyFFI compatible libraries with the D compiler.
In the next part in the series I will be talking about some D code I have written to automatically generate the correct RubyFFI bindings directly from our D code.