But be warned: everyone who is more experienced and skilled might read my following instructions in disgust or horror. So I wouldn’t recommend any of it. But what can I say: for me it has been working so far, without any issues and stable. Something done properly is for sure best, but if “properly” isn’t available then in many cases “it works” is still a great option.
You have a few options how you want to handle the applications start. It’s the easiest if your application starts within crystal-code (this sounds less flexible than it actually is: your other code (for the rest of this post let’s assume it’s C, as it’s easier to type “C” instead of “other language”), so your crystal code, could just call C’s main
function, which you simply call notMain
and the only thing you do at the start in crystal is to call C’s notMain
(you do that just via the lib… fun … stuff as crystal offers it). For the application’s user it’s the same like if the application started with the C code, but it’s a lot easier this way.
Then the crystal methods, which you want to call from within C you have to define not via def
but with fun
in crystal. In C, you only write the definition for those methods, but no implementation. When you want to link the code, the linker might complain about missing symbols. If that’s the case you have to fix the name mangling (I’m pretty sure this could be automated via some macros, but it’s so easy, that writing a macro is probably not worth the effort). Just look via “nm” what name your C object needs, and add it as an external name in your crystal code (like fun myMethodsNormalName=“myMethodsMangledName”). Nice side effect: once you are able to get the mangled names right by yourself, you don’t even need to create an object file for your C code, but can just let the crystal build
command take your C files as linker flags (@[Link(ldflag:“mysource.c”)] and it get’s all compiled just within your usual build command (at least if you use just clang as your linker anyway - as those files would be just compiled and linked in the same step, when crystal just intended to link libraries). If it was crystal which started your application (as in it was crystal’s main method which got started in the first place) you shouldn’t need to do anything else.
Kinda think of it not as using libraries. Just as if it would be all one language. And you declare and implement things just in separate files (especially if you view it not like with Crystal where those two steps are just one, but like you have it with C usually anyway with header files). You declare things on both sides, so you can use them in both worlds, but you implement it only in one (as often as possible in Crystal as it’s easy and we love it, and as often as necessary in C or whatever).
You also can start your application within C, but you will have to start Crystal’s GC.init
and LibCrystalMain.__crystal_main
in the beginning (=before you call anything else of your crystal code) or you will get segmentation faults as soon as you do anything relevant. Also crystals main
method might upset your linker (as in having suddenly two main
symbols). But simply rename again your C’s main
to notMain
, and tell the linker, to use that as the starting method (-e "_notMain"
).
If you work with classes, you will get quickly frustrated with all the helper methods you have to introduce. You absolutely can implement C class methods in crystal (top level) methods directly, just like top level methods once you fixed the mangled names. If you want to call instance methods, or do Crystal class methods, it is still possible, but requires to tinker with crystal’s compiler (to do this it’s enough to comment out one line - you will create double the amounts of symbols, but the additional symbols won’t conflict with any relevant symbols, so they won’t bother you much).
To write the instance methods of one language in another, it needs a lot more tinkering (it works though; I am currently trying to figure out, how this could be done properly (without generating dozens of unnecessary symbols) and “magically” doing the addressing of the right instance automatically - it works, but at least I need to do a lot of manual stuff to get it working (someone who knows what they are doing, might handle this probably a lot easier - I’ve no experience at all in any of this)).
If the instance would have any variables, you will have to decide for each class on one language where you want to have them “living”, allocated and eventually released (it can be either, but if you mix it, it get’s very quickly messy, and it will be already messy enough, so just pick one side for the better). You can still access the variables in either language (just not directly, but only via getter/setter methods, which doesn’t make a difference anyway other then you loose the public/private/… choice; those getters/setters you just access again like any other instance method).
If you plan on simply wrapping your crystal code in a C library to call it from ruby - I’ve tried exactly this and sadly it didn’t work (but again - it’s not impossible, that I’m doing something wrong on ruby’s side, as I’m really an absolutely noob when it comes to libraries and linking and wrapping). Within the C-ish family though it seems to work pretty well, especially if you stay top-level on both sides (C class methods are also okay). Instance methods, variables, or even just Crystal class methods, kinda work, but for me so far only with modification of the compiler (for now, you probably better just use auxiliary methods to get things to top level).
Oh, and you should have all your Crystal things within one crystal application (can be dozens of files, but I mean, don’t use it like you would usually do with modules; you will use only one crystal module, but it can contain hundreds of unrelated functions) which was built just with one crystal build
command.
It’s already past midnight on my side. Tomorrow I’ll upload some small example to play with.