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.