I’m thinking about generating a wrapper for a pure C library (raylib). So far I have found two options for doing that (semi-) automatically: Clang and CBindingGen.
Clang seems to be the default option, but while documentation for both is on the sparse side, CBindingGen appears to be slightly more accessible (I managed to generate and load bindings in about 30min).
Does anybody have any experience with either of them and can give me some pros and cons to help me with the decision which one to use?
Edit: added links
If it helps at all, this is the first time I’ve heard of CBindingGen and it’s unclear from their docs why it’s being developed instead of contributing to Clang.jl (I’m sure there’s a reason, but I couldn’t find one written down)
CBindingGen.jl is based on
CBinding.jl, which overcomes some limitations of Julia’s standard C interface,
Clang.jl uses Julia’s standard C interface. @krrutkow can perhaps offer some more insights (albeit biased )
Yes, same for me, therefore my question. I had assumed Clang was the only option and found CBindingGen only by accident.
In short, the goal of Clang.jl is to generate self-contained Julia wrapper code for the C API. The generated code is in pure Julia style.
The goal of CBindingGen.jl is to generate Julia wrapper code using CBinding.jl’s macros. The generated code is like C code.
I’m reworking the package to make it easy to use, see Rework the package by Gnimuc · Pull Request #278 · JuliaInterop/Clang.jl · GitHub.
This is how I generate the bindings with #278,
➜ git clone https://github.com/raysan5/raylib.git
Cloning into 'raylib'...
remote: Enumerating objects: 69, done.
remote: Counting objects: 100% (69/69), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 28173 (delta 37), reused 42 (delta 21), pack-reused 28104
Receiving objects: 100% (28173/28173), 352.88 MiB | 2.83 MiB/s, done.
Resolving deltas: 100% (20012/20012), done.
Updating files: 100% (885/885), done.
➜ cd raylib
➜ mkdir gen && cd gen
➜ cat wrap.toml
library_name = "libraylib"
output_file_path = "./LibRaylib.jl"
module_name = "LibRaylib"
use_julia_native_enum_type = true
➜ cat wrap.jl
const RAYLIB_H = [joinpath(@__DIR__, "..", "src", "raylib.h")]
options = load_options(joinpath(@__DIR__, "wrap.toml"))
args = String
ctx = create_context(RAYLIB_H, args, options)
➜ julia wrap.jl
[ Info: [CollectTopLevelNode]: processing header: /Users/gnimuc/Codes/raylib/gen/../src/raylib.h
[ Info: Building the DAG...
[ Info: Emit Julia expressions...
[ Info: [ProloguePrinter]: print to ./LibRaylib.jl
[ Info: [GeneralPrinter]: print to ./LibRaylib.jl
[ Info: [EpiloguePrinter]: print to ./LibRaylib.jl
[ Info: Done!
Wow, thanks for the comprehensive walkthrough.
That looks pretty straightforward. I’ll give it a try as soon as I have time and decide based on how much I like both packages’ output .
CBinding was necessary to more accurately support C code, but it had its quirks. CBindingGen was an alternative to Clang (which has its own quirks) that used the CBinding syntax to less manually create the bindings.
The CBinding* framework was an experimental and educational step though, and we are working on releasing a new related package this week that you all might find rather interesting.
I finally got around to giving this a try and I have to say the generated code looks super-pretty! If it works as well as it looks this is definitely a winner.
Works great save for macros and varargs (not implemented yet, as mentioned in the PR @Gnimuc linked to). Also, the library name in the .toml file needs to be a symbol (so “:libraylib” in my case).
But I can definitely work with this and the generated Julia API seems to be much nicer than CBindingGen.
You may also be interested in @krrutkow’s new work: GitHub - analytech-solutions/C.jl: Automatic C interfacing for Julia.
The new string macro just allows users to write exactly C code, which, I think, is way better than CBinding.jl’s Julia-C-hybridized macros.
As I mentioned above, the goal of Clang.jl’s generator is to generate code that a user will write based upon Julia’s C interop docs. The goal of CBinding.jl/C.jl is to get a better user experience when working with the C interface.
Also, the Julia package curators are not a fan of the C.jl package name, so package functionality will probably be merged into CBinding.jl
And for historical reasons, the name of Clang.jl is also to some extent misleading. Clang.jl should only host a thin wrapper over libclang or even Clang, but I guess it’s too late to make the change.
Hah! “Thin” is a subjective term, so no worries!
For someone who might be interested in the interop between Julia and C, here are some recent PRs/Issues related to the topic:
Off-topic, but I’ve been waiting for #32658 to finally be merged for ages. It’s really slightly embarrassing that Julia still has no simple, straightforward way to declare mutually recursive types.
The raylib bindings using CBinding.jl v1.0:
CMAKE_INSTALL_PREFIX = "$(dirname(@__DIR__))/deps/usr"
c`-I$(CMAKE_INSTALL_PREFIX)/include -L$(CMAKE_INSTALL_PREFIX)/lib -lraylib`
const c"va_list" = Cvoid
You can add ‘w’ to the string macro options to wrap the inline functions as well, but that is more experimental functionality.
That’s also quite lean! But if I understand the documentation correctly the generated bindings have to be used via the c"" string macro, right?
With the ‘j’ string macro option applied, Julian names are also created. So in this example you can directly use
libraylib.Transform for instance.
As you mention, bindings are always generated in the
c"..." symbol “namespace” to avoid name collisions (that approach is 100% foolproof). They are optionally mapped into the Julian symbol “namespace” if the user determines no name collisions will occur and requests that functionality.
@mhinsch I’m also interested in an Raylib binding. If you have something working would you mind uploading it to GitHub?
Unfortunately more urgent (as in I’m actually getting paid to do them) things got in the way and will do so for a while. I still intend to get back to the raylib wrapper, but it might be a couple of months before I have the time to do so.