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.