The second is that many semantics just don't map very easily. I imagine the latter problem has only gotten worse with heavier use of templated types in APIs. It looks like CLIF doesn't expose the template per se (at least in the Python examples) but requires explicit instantiation of each specialization as a distinct Python class. Templates mess with the very concept of an FFI generator, which tends to assume a reasonably clear separation between compile time and run time (so you don't need to run a C++ compiler as a pre-pass on any code that uses a binding).
All that is a roundabout way of saying: for god's sake write your system APIs in C.
Once you have a little C++ code, the easiest thing to do is to continue writing more C++ code, even if it's not the best tool for that job. It's too hard to use a C++ interface from any other language. Even wrapping it in C is annoying, although as I understand LLVM does exactly that for some of its API.
IMO there are a lot of systems where C++ is the best language for about 10% of the code. C++ really is unique in terms of offering zero-cost abstraction. But then the remaining 90% gets written in C++ too. It can be remarkably awkward for many problems, and your build times scale nonlinearly too.
In some way this is inherent ... the compiler's job is to erase all those abstractions and generate straightforward machine code. In other ways it is the fault/result of adhering to the C linking model, which ironically was for interoperability.
C++ is sort of like a universal receiver and not a universal donor. It can assimilate any C code, and thus it gets code in other languages transitively. But other languages can't assimilate it, at least not without great effort.
That said, Clang has a better API than GCC-XML, so the problem might be solved. Athoough honestly C++ just keeps growing more features with C++ 11, 14, 17 that make it harder to interoperate with any other code. It's this huge compile-time language completely separate from the C linking model.
D has the best support for this. It's not as simple as `#include <cppheader.hpp>` but it's better than any other language at it. Name mangling matches, C++ exception support and C++ abstract classes can be declared as D interfaces and everything works.
> C++ really is unique in terms of offering zero-cost abstraction
Rust and D (at the very least) would disagree.
This is part of the language's design.
Bjarne's goal after being forced to use BCPL instead of Simula was never to use a bare bones language ever again, and C with Classes needed to fit into AT&T's C tooling.
Hence why we also didn't got modules back then and a funky name mangling to fit into those bare UNIX linkers.
As for being an universal donor, the story is a bit different in OSes where the ABI is not C based, e.g. BeOS, Symbian, Genode, Windows (COM, .NET, UWP), OS/400 (TIMI), z/OS (ILC).
(edit) Clang's internals are also several orders of magnitude easier to work with than GCC's internals. In fact, clang is designed to be used as a library, and CLIF leverages that very, very highly.
See, for example, https://clang.llvm.org/docs/ClangTools.html . Clang tools are very powerful, and much of CLIF is based on a Clang tool, not a GCC internals hack.
I know that LLVM is notable for not maintaining API stability -- not sure about Clang.
Or at least expose a C-compatible ABI.
Much better and we get to use a modern OO API.
Hence why better Windows support is relevant for Rust, if you want it to be more seriously taken by Windows devs.
EDIT: Same applies to mainframes, which also don't follow a C ABI, rather their own native languages.
And cliff takes care to error out when the wrapper description doesn't match the C++. "When in doubt, refuse the temptation to guess."
Our internal clients really like it. Someone described it the other day as "magic".
No. Use an IDL to define component interfaces and a generator for any particular language. That's how C++ components interoperate on Windows. (COM is just a formalization of vtables generated from an IDL file and compiled with midl compiler.)
Interesting stat: Around 80% of our the Python C++ extension module wrappings being added to our code base are now being done using CLIF instead of SWIG. We are actively working on forbidding new Python SWIG wrappers from being added and migrating important legacy wrappings off of it onto CLIF.
Who am I? I am TL of the team that create CLIF (design and code reviewer, _not_ a primary author; they can identify themselves as they see fit). -gps@
It takes time and because we love Python we did it first. Others will come.
C++ and Python are not very similar, and the result of auto-binding them typically gives up a combination of fluency and performance.
A simple example of the former is a C++ algorithm which writes to an output iterator. In Python this might be expressed as a generator. But none of the auto-binding tools can do this transformation.
As for the latter--performance--I have seen 100x performance penalties in practice when the blind lead the bind. A good example is that in Python we have memoryview and the buffer protocol, but auto-binding tools take these no further than std::vector (if that). A C++ API which produces a large stream of numeric data just begs to be bound using the buffer protocol or perhaps even NumPy directly. But if a C++ API produces small values really quickly, an auto-bound one will produce the same small values really slowly.
Some people see the writing of bindings as manual labor to be avoided. I see it as an optimization opportunity. There are huge gains available.
Roughly the same experience. Automatic generation for anything more than trivial examples always seems to lead to the need for more and more configuration to keep everything in line, to the point where just writing everything by hand becomes less work. This is likely due to the type of software we use it for, but still, the amount of time I lost on SWIG really seems wasted instead of leaving the feeling of having learnt at least something. It was all not very pleasant. The Python side of our software gets exposed to less tech-savvy users and is meant to be a more friendly layer over C++ functions and classes. But the C++ side isn't really an API in the sense there is no hard distinction between the 'internal' layer and the API layer. Some class which is only used internally might the next day also be wrapped to be available in Python. So there's no single directory or so one can point to and say 'everything in there is the API'. Also given a C++ class, sometimes only a couple of methods have to get exposed to the users, sometimes with a different name even, or some arguments defaulted etc. Preferrably without having to write a wrapper just for the sake of exposing it. All this turned out to be a nightmare in SWIG. Just manually writing one or more lines for registering the function manually (not Boost.Python but similar) is usually a one-time-almost-never-look-back thing which, for us, is way less work in the end. Even with the hundreds of functions we have already. And of course performance is the other advantage.
But a "there be dragons" caveat applies as getting the CPython API correct is complicated. Reference counting and error checking bugs are common in hand written CPython C API code.
For most cross language bindings the primary goal is to "just work reliably". Optimization can happen later after you have profiles to figure out where it is worth doing and in what way.
$LANG (e.g. Python) <-> JSON <-> Pipe <-> JSON <-> C++
If this is too slow I still wouldn't use a binding generator (SWIG, etc.), but build a C API using opaque pointers that offer the required functions, i.e. a bridge between C and C++. Finally one can simply call into this API with Python's ctypes which are part of its standard library. Python <-> ctypes <-> C-API <-> C++
I have worked previously with SWIG, but my experience has been very bad, in particular the problem with binding generators is similar to the problem parser generator face - they make the simple stuff simple and the hard stuff hard. Furthermore the bindings that are auto-generated are mostly too low-level, so one normally writers wrappers around them to make them more "pythonic" which raises the questions why not write the bindings from scratch anyway?Not to say SWIG isn't an amazing effort, but it's a whole C++ compiler maintained by a very small team.
+ I already put my time in at the gcc salt mines so no, I didn't try doing this myself.
[0] Actually, there is one tiny exception: CLIF occasionally separates a C++ qualified name on the scope-resolution operator "::".
SWIG should be called WIG. And anyway, I can't see it surviving modern C++.
As long as I'm ranting, what is modern C++ but a bunch of new languages that are incompatible with C++, each other, and everything else? Forgive me if I'm wrong but this seems like a terrible idea.
Many of the idioms actually already possible back in the C++ARM days, before C++98 was a thing, but spoiled by C refugees.
- Calling a native function that itself takes callback, and that callback might be your non-native code. (trampolines?)
- Dealing with memory allocation. Who owns what.
- Exceptions or long_jmps
- Green threads, fibers, etc.
- Other? - int64 -> uint64 conversion
- int64/uint64 -> double conversionA good README should give a "high level" description of what the things is and what its used for.
While I think wrapper generators are a noble idea, I doubt I would choose this over the convenience of IDL-like metaprogramming approaches (e.g. pybind11, or Boost.Python)
What if I also wanted my wrapper available on a non CPython VM? The proper way to use PyPy is not via its fake CPython API support (slow and memory hungry). Imagine if a PyPy Generator were added to CLIF. It'd use the same interface definition Parser but generate an entirely different set of code. In PyPy's case that would probably be a C library wrapping the C++ for generated cffi based Python code to interact with.
Admittedly all hypothetical here until other Python VMs have CLIF Generator implementations.
I think the author of Swig shares this sentiment: (http://code.activestate.com/lists/python-dev/109281/).
However: Some libraries/packages do expose a C api, which you could use in a language assuming it had a decent FFI.
A lot of major C++ projects use exceptions, which you will have to catch and handle inside of the C++ because even if C could catch exceptions, there is no guarantee of ABI compatibility. Some languages, I think D can do it, can catch an exception thrown in C++ code.
You might have better luck integrating a standard data interface between the C++ and any other language: However, that could be a little slow.
Typical usage I saw is the following: C++ has some class(es) that best represented as NumPy arrays. So because those classes are specific to the project (not generic like std::) they don't fit into CLIF runtime, but the project supply a C++ library with custom conversion functions as described in ext.md that use NumPy C API tells CLIF that those classes are convertible to Python objects (that NumPy objects are).
That all NumPy integration CLIF needs and it has to come from the user.