Gem #59: Generating Ada bindings for C headers

by Arnaud Charlet —AdaCore

Let's get started…


GNAT now comes with a new experimental binding generator for C and C++ headers which is intended to do 95% of the tedious work of generating Ada specs from C or C++ header files. Note that this is still a work in progress, not designed to generate 100% correct Ada specs.

The code generated uses the Ada 2005 syntax, making it easier to interface with other languages than did previous versions of Ada, in particular via the generation of limited with clauses and anonymous access types.

In this Gem, we will focus on the generation of C bindings. Bindings to C++ will be addressed in a future Gem.

Running the binding generator

The binding generator is part of the GCC compiler that comes with recent versions of GNAT and can be invoked via the -fdump-ada-spec switch, which generates Ada spec files for the header files specified on the command line, and all header files needed by these files transitivitely. For example:

$ g++ -c -fdump-ada-spec -C /usr/include/time.h
$ gcc -c -gnat05 *.ads

generates, under GNU/Linux, the following files: time_h.ads, bits_time_h.ads, stddef_h.ads, bits_types_h.ads, which correspond to the files /usr/include/time.h, /usr/include/bits/time.h, etc..., and then compiles these Ada specs in Ada 2005 mode.

The -C switch tells GCC to extract comments from headers and attempt to generate corresponding Ada comments.

If you want to generate a single Ada file and not the transitive closure, you can use the -fdump-ada-spec-slim switch instead.

Note that we recommend, where possible, to use the G++ driver to generate bindings, even for most C headers, since this will in general generate better Ada specs. If G++ doesn't work on your C headers because of incompatibilities between C and C++, then you can fall back on using GCC instead.

For an example of better bindings generated from the C++ front end, the name of the parameters (when available) are actually ignored by the C front end. Consider the following C header:

extern void foo (int variable);

With the C front end, "variable" is ignored, and the above is handled as:

extern void foo (int);

generating the following subprogram specification:

procedure foo (param1 : int);

With the C++ front end, the name is available, and we generate:

procedure foo (variable : int);

In some cases, the generated bindings will be more complete or more meaningful when defining macros, which you can do via the -D switch. This is for example the case with Xlib.h under GNU/Linux:

g++ -c -fdump-ada-spec -DXLIB_ILLEGAL_ACCESS -C /usr/include/X11/Xlib.h

The above will generate more complete bindings than a straight call without the -DXLIB_ILLEGAL_ACCESS switch.

In other cases, it is not possible to parse a header file in a stand-alone manner, because other include files need to be included first. In this case, the solution is to create a small header file including the needed #include and possible #define directives. For example, to generate Ada bindings for readline/readline.h, you first need to include stdio.h, so you can create a file with the following two lines in, for example, readline1.h:

#include <stdio.h>
#include <readline/readline.h>

and then generate Ada bindings from this file:

$ g++ -c -fdump-ada-spec readline1.h

In a future Gem we will talk about generating similar Ada bindings for C++ headers.