|
|
8 ヶ月 前 | |
|---|---|---|
| .. | ||
| cmake | 8 ヶ月 前 | |
| data | 8 ヶ月 前 | |
| docs | 8 ヶ月 前 | |
| examples | 8 ヶ月 前 | |
| expected-lite | 8 ヶ月 前 | |
| gi | 8 ヶ月 前 | |
| override | 8 ヶ月 前 | |
| test | 8 ヶ月 前 | |
| tools | 8 ヶ月 前 | |
| .clang-format | 8 ヶ月 前 | |
| CMakeLists.txt | 8 ヶ月 前 | |
| LICENSE | 8 ヶ月 前 | |
| README.md | 8 ヶ月 前 | |
| format.sh | 8 ヶ月 前 | |
| meson.build | 8 ヶ月 前 | |
| meson_options.txt | 8 ヶ月 前 | |
cppgir is a GObject-Introspection
C++ binding wrapper generator. That is, it processes .gir files derived
from GObject-Introspection annotations into a set of C++ files defining
suitable namespaces, classes and other types that together from a C++ binding.
In this way, the plain C libraries and objects become available as native
objects along with (RAII) managed resource handling. The generated code
only requires a C++14 compiler and library (as well as obviously the underlying
C headers and libraries that are being wrapped).
The code generator can be built using CMake or meson and requires Boost and either fmtlib or a C++20 std library with format support (e.g. gcc 13+). While it obviously depends on distribution, these may be typically be obtained by installing following packages;
libfmt-dev libboost-dev
If it is required to compile the manual page ronn is required to be installed
The following step will provide an additional dependency as a submodule;
git submodule update --init
With all that in place, the usual steps apply, so e.g.
mkdir build
cd build
cmake ..
cmake --build .
cmake --install .
or alternatively
mkdir build
cd build
meson setup . ..
meson compile
meson install
The installed components consist of:
The latter is needed when using the generated code, the former only at generation time.
In case of CMake build, it is also possible to build some examples whose sources are also installed along with some documentation. As such, the meson system only builds and install the (most) relevant parts, as may be useful when used as a subproject.
The generated code for an even-numbered major version should be (compile) API stable. If the major version turns odd, then a cycle/series of incompatible changes may occur, which after actual practice and time can then become the next even stable version.
All details can be found in the manpage, but a simple example wrapping GStreamer libraries is as follows:
generate --output /tmp/gi GStreamer-1.0
The above (obviously) assumes that suitable .gir files are present in
the usual location, as typically installed by a -dev package. It is
also assumed the generator has been installed (so "running installed").
Running the generator uninstalled is also possible, but then (if not embedded) the
default ignore files in data directory have to explicitly specified as well
(using either option or environment variable as specified in manual).
That's it, all wrapping code is now available in /tmp/gi (in a number of
subdirectories). It only requires a C++14 compiler and library and can be
included and compiled into the target library or executable. The latter then
has no additional dependencies other than the plain C libraries (gstreamer and
lower level ones in this case).
The wrapping binding code presents an API along the lines of any other binding, which also means it maps pretty straight onto the original library API. So all API is simply found where expected. To use it fully inline, the following include snippet suffices:
#define GI_INLINE 1
#include <gst/gst.hpp>
Of course, the include path must have been setup properly to contain the output
root of the generated code (i.e. /tmp/gi in previous example). It must also
contain the proper path for header code supplied by this repo (using e.g. a
pkgconfig fragment if it has been installed, or an IMPORT target or by other
means).
See the manpage for additional essential remarks on the API of the generated code or typical non-inline use of the code.
The examples (in the so-named directory) illustrate and cover various practical
aspects and use of the generated API. While they are all simple and trivial,
they do illustrate use in various domains such as Gio, Gst and Gtk.
For example, when it comes to traditional C libgio, there is usually a choice
between a synchronous (blocking) call and a corresponding asynchronous
(non-blocking) call. The latter is then typically used to establish a chain of
completion handlers. In other settings and languages, it has become customary
to employ async/await operations/keywords. That way, the flow of code remains
linear as in the blocking case. However, some form of callback chain is
typically present behind the (implementation) scenes and a more practical
consequence is that async/await keywords need to be sprinkled in quite some
places (down to call stack depth). That, in turn, is then intrusive/disruptive
in its own way.
Alternatively, one can use so-called stackful co-routines or fibers to maintain
stack context when execution has to be suspended (e.g. awaiting I/O, or some
condition). An example implementation is provided by Boost
Fiber
which caters for (cooperative) switching between multiple fibers on a single
thread. This is viable in C++ since such code should be exception-safe anyway
and as such should not be taken by surprise upon code execution not making it
all the way through a code sequence. After all, that might happen at any time
upon exception, as in the particular case of fiber context destruction in stead
of regular fiber termination. This approach also allows using legacy code in
multiple fibers on a single thread, since no additional synchronization is
required, and no special support (or keyword sprinkling) is needed along the
callstack (down to point of suspension). To come down to it, the async Gio
example provides a GMainContext based Fiber scheduler to integrate and
support using Fiber in a standard GLib mainloop setup. In particular, that
allows turning Gio's async calls into (apparent) blocking calls when run on a
fiber, which then leads to a concise sequential code flow.
A few simple but nice to have features of the generated code are:
Another feauture might be handy even if one is not interested in generated
C++ code. As the .gir files as processed, a number of consistency checks
are performed and warned about. In that way, the generator also acts somewhat
as annotation validator, and can typically spot some missing (out) or
(array) (and related) annotations.
Some other features have been implicitly mentioned above, but are perhaps best high-lighted by addressing how it differs from gktmm (and the related question; why another C++ interface). To this end, first note or recall that gtkmm consists of many repositories (glibmm, gtkmm, gstreamermm, ...), all of which typically also yield a distribution package for library code and headers. So, when using e.g. gstreamermm, several such packages are required, either for their headers at compile time or for the libraries at runtime. In contrast, only 1 repo is needed here, and using the generated code then incurs no additional (runtime) dependencies other than the (unavoidable) C libraries.
The code in gtkmm's (and friends') libraries can be considered as hand-crafted. Well, it is produced based on templates, but those are manually maintained. It does not use or consider the GObject-Introspection annotations in any way (not in the least because it predates that system). So, as a C library and API evolves, the corresponding gtkmm has to be manually synchronized. That is, if there is at least a corresponding gtkmm one. For a not-so-well-known-or-popular one there may not be, and definitely not for a custom developed one. On the other hand, whenever (minimal) annotations are available, you are good to go with any binding, whether PyGObject or the one provided here.
Since the gtkmm code is hand-crafted API from the ground up, that allows for
some nifty things (such as the signal approach mentioned further below). On the
other hand, sometimes it might be too nifty in that the C++ API does not quite
track or match the original one. Instead, it is then more of an alternative or
parallel one. For example, in GStreamer, there is (only) one (mini-object)
C-type of GstEvent (with a type field indicating the precise type of event).
So it is handled this way throughout the API and in bindings (e.g. PyGObject).
However, in gstreamermm it has been chosen to have many types (i.e. subclasses)
of Gst::Event. This may well be a natural C++ way (or a Python one if done
with Python classes), but all together it presents an API where things are not
quite where expected, and it is in that regard not a straight binding. In
contrast, the generated code here is as straight as can be, and functions to
call are right where the fingers expect them to be, whether in PyGObject or
here.
So, in the concept of "C++ binding API", gtkmm has emphasis on C++ up to the point of almost a parallel or independent API with quite some trimming. The latter is illustrated by the separate libsigcplusplus (with similar notions in e.g. boost signals2). Here, however, the emphasis is on binding.
Of course, other than this straight binding, one is still free to use and bring in any other lib, e.g. one of the aforementioned signal helper libs. However, in most cases the "straight bindings" will suffice. Moreover, a few small additional RAII helpers are provided that may be useful. See the examples for an illustration on how they can be used.
A compile-time binding is somewhat different than a typical more runtime script language binding (e.g. Python). While most of the annotated API is usually well enough handled and covered for practical use, there are some limitations and issues to consider. Again, more details and further remarks are provided in the manpage