mosra/magnum

C Bindings

crystalthoughts opened this issue · 5 comments

Hi, are there any plans to provide pure C /other language bindings? I don't want to use C++ but your project looks great!

Hi!

Good question, actually. What exactly is your use case? Are you using C directly, or want to use Magnum from Rust or some other language and can't because calling into C++ from there is painful? Or do you simply hate C++? All those are valid reasons, I just want to understand your case better. I don't like the C++ STL for example and have my opinion about everything beyond C++11, so the code is deliberately a simple-to-grasp subset of C++11 and none of the crazy stuff is used.

In case you'd be fine with Python, there's Python bindings for an increasingly large subset of the API.

But for C... The thing is, a lot of the interfaces for importing, processing or exporting data is built around strided array views, which make the APIs extremely powerful without being hard to use. This concept maps directly to Python thanks to its buffer protocol interfaces, which means it's possible to use those powerful APIs conveniently from Python as well, without giving up on efficiency, as passing data this way is zero-copy. But when those would be exposed to plain C, you'd basically get a void* pointer together with type, (multi-dimensional) size and stride information, and similarly each function would have to take a pointer, size + stride, which isn't nice to use. OTOH restricting the APIs to always accept just contiguous arrays would throw away a lot of the flexibility compared to C++.

Similar case is for example with the StringView class, in C++ it's able to remember whether given string is null-terminated or a global literal, and a lot of internal optimizations make use of this information to avoid needless copies, to skip making a null-terminated copy, etc.. When exposed via plain C interfaces you'd then either have to pass this null-termination / global flag every time to retain the flexibility (which would be extremely annoying), or the APIs would have to be simplified to not contain this information at the cost of being less efficient than what would be doable with the C++ API. This doesn't map efficiently to Python unfortunately (as IIRC it stores the string as ASCII/UTF-16/UTF-32 depending on the most complex characters it contains) so in the Magnum Python bindings it's always making a copy.

Finally, there's a lot of templated math classes that would need to get stamped out for each type (8-, 16-, 32-bit signed/unsigned integer vectors, floats, half-floats, doubles...) and I don't see a way how this could be done in a way that doesn't make everything extremely annoying to use or maintain. In Python I trimmed down the type variety to just 32-bit ints, floats and doubles but even then it's still a lot of variants that make the bindings rather large.


On the other hand, there's an increasing amount of algorithmically-dense APIs (SIMD-optimized string lookup functions, float/integer packing/unpacking, or for example SPIR-V patching) that I think could make sense to expose to C directly, eventually, to enable a broader range of use cases. But as standalone "single header" libraries, without the whole Magnum around. There's already several single-header C++ libraries exposed this way, for the potentially exposed algorithmically-dense code it'd mean adding some #ifndef __cplusplus with a plain C API that wraps the functionality, while still compiling the implementation as C++.

It's a contentious topic, but as an outsider to C++ development, there is so much to learn before you can be productive. The surface area of the language is huge, there's a moving subset of language features seen as best practice. The build chain requires external tools (i.e. CMake) and choosing a compiler, all of this has to be learned too. Then you have to deal with separate headers and source files which feels unnatural and frankly pointless to people who didn't cut their teeth on it.

I want to pick a simpler systems language (Zig, Odin), learn your api and just code what I need to using structs and functions. Both support custom allocators and things like defer. The build system is the language itself, and they currently both just target LLVM as a backend. People have created bindings for some of the more important libraries already. If I want to leverage language-level abstractions like some of those you mention, I would pick something like Janet and use macros.

Very few languages support easy C++ bindings, but everything supports C. It would really create a much larger pool of people willing to adopt this framework.

If you could provide a json file or similar outlining the API , it would be even better as then the bindings can be automated somewhat. I wish the programming world was less siloed!

Odin also supports a lot of that array functionality too, setting the stride, alignment, multi-pointers (slice-like pointers) etc.

For the data types, perhaps going the python way of just 32-bit for now might work.

But I appreciate its probably a lot of work if not architected that way to begin with!

Yeah, this project started in 2010 when none of these languages really existed. If I would be starting a new project nowadays, maybe I would choose a different main language. Or maybe not. C++ is still one of the main languages large projects get written in so the "pool of people" who know it (compared to, say, Zig, Odin or Rust) is large enough. And compared to plain C, with C++ most of the footguns can be minimized with a sane choice of API.

One of the selling points of this library is that it's "batteries included" and the C++ API is designed to be nice to use. I.e., you get a ready-to-go solution where you don't need to start wrapping it in nicer / safer abstractions as the first thing, compared to having an interface in plain C. If this project would expose just a C interface it would inevitably lead to people trying to poorly wrap it in C++, creating a giant mess like happens with practically any C API ("Modern" C++ "wrappers" for SDL, for Vulkan, for curl...). To avoid that and retain control over API usability, I'd need to maintain both a C++ API and a C API, and I simply don't have bandwidth for that. The API surface of Magnum is large, even the Python bindings, where I can call directly to C++, take a lot of effort, not to mention writing complete documentation for all that as well.

From my perspective, the subset of C++ needed to use Magnum isn't that far from C alone. You don't need to even know about templates, and you can directly reuse the C-buildsystem-related knowledge, that's not different at all. And even though it's nice one can use Zig to build Zig code, when dealing with mobile platforms you still get to touch the worst ecosystems and tools ever made anyway, there's no way around that.

TL;DR: As the situation is now, there simply isn't a possibility for me to start providing an alternative C, Zig etc. API anytime soon, apart from the select cases listed in my first comment. Such bindings would need to be a completely community-driven project. Sorry.

No worries, was just curious. Thanks for your input and your years of work on the project! I'll definitely give it a go in the future.