File: README.md

package info (click to toggle)
cppgir 2.0%2Bgit20250629.2a7d9ce-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,220 kB
  • sloc: cpp: 16,451; ansic: 355; python: 86; makefile: 13; sh: 9
file content (221 lines) | stat: -rw-r--r-- 10,737 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# cppgir

`cppgir` is a [GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
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).


## Installation

The code generator can be built using [CMake](https://cmake.org) or
[meson](https://mesonbuild.com/) and requires
Boost and either [fmtlib](http://fmtlib.net/) 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 code generator executable
* (if not embedded by build option) default ignore files
* common headers required by the generated code and default overrides

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.

For either build system, an [example](examples/external) shows how `cppgir`
can be used in a project.

## Release numbering and API stability

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.


## Running

All details can be found in the [manpage](docs/cppgir.md), but a simple
example wrapping [GStreamer](https://gstreamer.freedesktop.org/) 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).


## Compilation

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](docs/cppgir.md) for additional essential remarks on the API
of the generated code or typical non-inline use of the code.


## Examples

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](https://www.boost.org/doc/libs/latest/libs/fiber/doc/html/index.html)
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.


## Features

A few simple but nice to have features of the generated code are:

* well structured and easily understood
* it can be used either fully inline or have implementation code compiled
  into one or more object files for subsequent linking
* it allows for extensions/overrides (similar to e.g. PyGObject)
  supplied by separate files (so not in the generated ones),
  either default ones supplied along with this repo or added custom by
  the project using the generated code

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](https://www.gtkmm.org)
(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](https://github.com/GNOME/libsigcplusplus) (with similar
notions in e.g. [boost signals2](https://github.com/boostorg/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.

## Limitations and Remarks

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](docs/cppgir.md)