Gem #60: Generating Ada bindings for C++ headers
by Arnaud Charlet —AdaCore
Let's get started…
Generating bindings for C++ headers is done using the same options as for C headers, again using the G++ driver. This is because the binding generation tool takes advantage of the full C and C++ front ends readily available with GCC, and then translates its internal representation into corresponding Ada code.
In this mode, C++ classes will be mapped to Ada tagged types, constructors will be mapped using the CPP_Constructor pragma, and, when possible, multiple inheritance of abstract classes will be mapped to Ada interfaces.
A complete example
For example, given the following C++ header file called animals.h:
class Carnivore { public: virtual int Number_Of_Teeth () = 0; }; class Domestic { public: virtual void Set_Owner (char* Name) = 0; }; class Animal { public: int Age_Count; virtual void Set_Age (int New_Age); }; class Dog : Animal, Carnivore, Domestic { public: int Tooth_Count; char *Owner; virtual int Number_Of_Teeth (); virtual void Set_Owner (char* Name); Dog(); };
We generate an Ada package with:
$ g++ -c -fdump-ada-spec animals.h
which generates a file animals_h.ads that has the following contents:
package animals_h is package Class_Carnivore is type Carnivore is limited interface; pragma Import (CPP, Carnivore); function Number_Of_Teeth (this : access Carnivore) return int is abstract; end; use Class_Carnivore; package Class_Domestic is type Domestic is limited interface; pragma Import (CPP, Domestic); procedure Set_Owner (this : access Domestic; Name : Interfaces.C.Strings.chars_ptr) is abstract; end; use Class_Domestic; package Class_Animal is type Animal is tagged limited record Age_Count : aliased int; end record; pragma Import (CPP, Animal); procedure Set_Age (this : access Animal; New_Age : int); pragma Import (CPP, Set_Age, "_ZN6Animal7Set_AgeEi"); end; use Class_Animal; package Class_Dog is type Dog is new Animal and Carnivore and Domestic with record Tooth_Count : aliased int; Owner : Interfaces.C.Strings.chars_ptr; end record; pragma Import (CPP, Dog); function Number_Of_Teeth (this : access Dog) return int; pragma Import (CPP, Number_Of_Teeth, "_ZN3Dog15Number_Of_TeethEv"); procedure Set_Owner (this : access Dog; Name : Interfaces.C.Strings.chars_ptr); pragma Import (CPP, Set_Owner, "_ZN3Dog9Set_OwnerEPc"); function New_Dog return Dog'Class; pragma CPP_Constructor (New_Dog, "_ZN3DogC1Ev"); end; use Class_Dog; end animals_h;
As you can see, C++ classes are mapped to Ada tagged types and even interfaces when possible. Another advantage of directly using the C++ compiler to generate bindings is that the information about C++ mangling is readily available and can be generated easily, while writing it manually can be tedious and error prone.
The C++ mangling is the low-level name chosen by the C++ compiler for a given function, such as "_ZN3Dog9Set_OwnerEPc" in the case of procedure Class_Dog.Set_Owner in the example above.
In the next Gem, we will explore in more detail how the CPP_Constructor pragma works and how to combine it with Ada features.