3

I would like to call D code from Ruby. I have tried to compile the D code with dmd and use extconf.rb to make a shared object file which I could use in ruby, but my linking fails somehow, the D std library is apparently missing:

    hello.rb:1:in `require_relative': /tmp/druby/hello_c.so: undefined symbol: _D3std5stdio12__ModuleInfoZ - /tmp/druby/hello_c.so (LoadError)
    from hello.rb:1:in `<main>'

Please let me know how to call D code from Ruby.

The code I tried is here:

    mkdir -p /tmp/druby
    cd /tmp/druby
    cat ->hello_d.d <<EOF
    import std.stdio;
    // a D function that we would like to call from ruby
    extern(C) void hello_d() nothrow {
        try { writeln( "hello from d"); } catch( Throwable t) {}
    }
    EOF

    cat ->hello_d.c <<EOF
    /* This is a dummy file to trick extconf.rb to include the hello_d.o file, surely this could be done from extconf.rb as well, but how? */
    EOF

    cat ->hello_c.c <<EOF
    #include <stdio.h>
    #include "ruby.h"

    /* c function */
    void hello_c(){
        printf( "hello from c\n");
    }


    /* ruby function for hello_c */
    VALUE method_hello_c( VALUE self){
        hello_c();
        return Qnil;
    }


    /* ruby function for hello_d */
    VALUE method_hello_d( VALUE self){
        if( !rt_init()) { return 1; }
        hello_d();
        rt_term();
        return Qnil;
    }


    /* ruby module and class definition */
    /* This method must be named "Init_#{filename.lower}" */
    void Init_hello_c() {
        VALUE hello_module = rb_define_module( "HelloCModule");
        VALUE hello_class  = rb_define_class_under( hello_module, "HelloC", rb_cObject);
        rb_define_method( hello_class, "hello_c", method_hello_c, 0);
        rb_define_method( hello_class, "hello_d", method_hello_d, 0);
    }

    EOF

    cat ->extconf.rb <<EOF
    # Loads mkmf which is used to make makefiles for Ruby extensions
    require 'mkmf'

    lib = File.expand_path('../../lib', __FILE__)
    \$LOAD_PATH.unshift(lib) unless \$LOAD_PATH.include?(lib)

    # Give it a name
    extension_name = 'hello_c'

    # The destination
    dir_config(extension_name,".")

    with_cflags('-fPIC -Wall -O3 -rdynamic -m64 -L/usr/lib/x86_64-linux-gnu -Xlinker --export-dynamic -Xlinker -Bstatic -lphobos2 -Xlinker -Bdynamic -lpthread -lm -lrt -ldl') do
        create_makefile(extension_name)
    end

    EOF

    cat ->hello.rb <<EOF
    require_relative 'hello_c'

    puts "hello from ruby"

    hello_c = HelloCModule::HelloC.new

    hello_c.hello_c( )

    EOF


    # 1. First make the hello_d.o file
    dmd -c -fPIC hello_d.d -defaultlib=libphobos2.so


    # 2. Make the ruby Makefile
    ruby extconf.rb

    # 3. Compile the shared library
    make

    # 4. Try to call it from ruby
    ruby hello.rb

    cd -
Niels Tolstrup
  • 457
  • 4
  • 12
  • Did you link in the Phobos shared library too? Looks like no, try adding `-defaultlib=libphobos2.so` to the `dmd` command. – Adam D. Ruppe Feb 10 '16 at 15:18
  • I have tried adding the libphobos2.so to the dmd command (see revised code above), but it does not change the outcome, thanks for the suggestion anyway. Also the phobos2 library was already in the cflags. – Niels Tolstrup Feb 10 '16 at 15:24
  • Your way of listing files and commands in a single code block is tedious to read. – weltensturm Feb 11 '16 at 08:19

1 Answers1

1

You can call D from Ruby using the Ruby-FFI extension. Take a look at this Wiki, which explains how to do exactly that with examples. Such as the following one:

Create the file "i.d" containing

import std.stdio;

extern(C)
void hello()
{
    writeln("hi from D");
}

Compile it as a shared library. For example, to compile as a 64-bit shared library on Linux, you can do

dmd -shared -m64 -fPIC -defaultlib=libphobos2.so i.d

Create the file "d.rb" containing:

require 'rubygems'
require 'ffi'

module DInterface
 extend FFI::Library
 ffi_lib './i.so'
 attach_function :rt_init, :rt_init, [], :int
 attach_function :rt_term, :rt_term, [], :int
 attach_function :hello, :hello, [], :void
end

# call init
DInterface::rt_init

# our function
DInterface::hello

# terminate
DInterface::rt_term

Run the Ruby file:

ruby ./d.rb

You should see hi from D

dhrubo_moy
  • 1,144
  • 13
  • 31
  • 1
    The FFI solution works well and I believe that currently this is the best solution. However maintaining and synchronizing objects between the Ruby and D runtime environment proves to be challenging due to the very limited FFI functionality. – Niels Tolstrup Nov 26 '16 at 11:33