Embeddable / Interoperable with ruby

My adventures with the subject:
I was able to compile Crystal code to shared library (MacOS dylib), link it with some glue code in C and load it as C extension in ruby. I was able to call Crystal fun from ruby, and that fun was able to instantiate object and call instance method on that object. Looks like GC works. No segfault for now
Crystal file:

class SomeClass
    def do_something(num : Int32)
        num*3+3
    end
end

FAKE_ARG = "crystal"

fun __crystal_init
    GC.init
    ptr = FAKE_ARG.to_unsafe
    LibCrystalMain.__crystal_main(1, pointerof(ptr))
end

fun __crystal_module_run
    puts "Crystal module run"
    test_add(1, 2)
end

def test_add(x, y)
    r = x + y + 3
    res = SomeClass.new().do_something(r)

    puts "Got #{r} and #{res}"
    res
end

res = test_add(1, 2)

puts res

Compile with

crystal build test.cr --link-flags "-shared" -o libcrystal_module.dylib

Ruby c extension code:

#include <ruby.h>
#include "extconf.h"

extern void __crystal_init();
extern void __crystal_module_run();


VALUE test_cfunc(VALUE _self, VALUE num) {
    int n = NUM2INT(num);
    VALUE result = INT2NUM(n+1);
    return result;
}

VALUE test_module_run(VALUE _self) {
    __crystal_module_run();
    return Qnil;
}


void Init_crystal() {
    VALUE mod = rb_define_module("TestModule");
    __crystal_init();
    rb_define_module_function(mod, "test", test_cfunc, 1);
    rb_define_module_function(mod, "dry_run", test_module_run, 0);
}

To link shared library to ruby C extension, I had to add

$LDFLAGS << " -L#{File.expand_path("#{__dir__}/../../crystal",)} -lcrystal_module"

to extconf.rb

Everything works as expected, but that is not much

Next, I need to process Crystal code and generate ruby wrappers, and load code dynamically.
First of all, I’ll try to compile Crystal code to MacOS bundle file and load it using dlload. I’ll add one more fun to Crystal code (lets call it __crystal_load_module) that will add Crystal wrappers to ruby.
I looked how Anyolite does that, and I’m thinking how can I either implement something similar or reuse existing code. I’m impressed what they were able to achieve with crippled Crystal macros and annotations.
I can’t link Crystal code to ruby API dorectly, so I think I’ll pass a struct with ruby API function pointers to __crystal_load_module.

2 Likes