Are there any tools or best practices for generating Zig bindings from a C library? Or possibly, even simpler than that, translating a C header into a rough Zig form that can be manually cleaned up?
I’ve tried using translate-c, but it fails to produce any output unless it has full visibility of all dependencies and the C code doesn’t include anything unusual like MS/GCC extensions. Instant failure for Win32 headers.
I’ve seen a lot of bespoke generator projects for specific C libraries (i.e. Win32 bindings, Vulkan bindings, etc.), but I’m curious if there are any more generic solutions. Rather than relying on (and possibly maintaining) multiple generator projects, I’d like to convert a bunch of headers to Zig with a single tool, clean them up, and then maintain them myself as needed from there.
Any solutions, or partial solutions, for doing this?
I doubt there’s any general solution currently existing. If there were, I imagine that the methods it uses would already have been adopted in translate-c.
By the nature of C compilation, there’s no getting around needing visibility of all header dependencies. Macros can be defined and used anywhere, so they all must be available.
Where an API has a metadata description (such as the winmd files for Windows, and some of the graphics systems), generators are a great solution.
Until translate-c can be brought up to snuff, probably best option to just slog out writing the Zig bindings manually. Sure, it’s tedious, and you have to understand what the heck all those layers of macros are doing, but you learn a lot about how the API you’re wanting to use actually works ;-).
I have a solution for the sokol headers, but it’s fairly specialized for the type of API used in those headers (e.g. there’s no support for things like unions or anonymous nested structs):
It’s basically a two-pass system, first a simplified JSON is generated from running the header through clang-ast-dump:
…then language-specific generator scripts take that simplified JSON as input and generate a specific binding, for instance for Zig:
…but I don’t intend to make the ast-parsing more flexible (I tried once by loading macOS framework headers in order to automatically create C API wrapper for ObjC APIs, but that was a PITA because macOS headers use a ton of non-standard annotations which one needs to sift through).
PS: my solution would suffer from the same problems that you have though: it will not see any code that’s inside an ifdef-block for which no define was provided, and it would fail to parse non-existing declarations (e.g. things pulled in from windows.h). It only works to create a binding for a platform agnostic public API, but not for anything that depends on platform-specific declarations.
That’s a really good point. I had considered trying your approach, but it looked like it would get complicated really fast for a generic solution. But it’s starting to sound even less possible with all the ways C headers can be weird.
Starting to look like my only good option is what pachde said, bite the bullet and manually write the bindings. Aside from time investment, the biggest issue there is trying to keep it in sync with future changes in the original C headers.
What is weird though is that the C header you’re trying to translate directly includes platform specific headers, because those headers would leak into all code which includes those headers also in a C project, which can open up quite a can of worms.
Usually the C header only declares a platform-agnostic API (which should at most need basic C stdlib headers like stdint.h, stdbool.h and stddef.h), and only the implementation should include platform specific headers.
I’ve also seen more than one library that include platform specific headers conditionally in an “interface header”, that wraps it all in an agnostic API, then depends on build options to configure the interface.