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
.