3.11.3.5 Interfacing with C++ at the Class Level

In this section we demonstrate the GNAT features for interfacing with
C++ by means of an example making use of Ada 2005 abstract interface
types. This example consists of a classification of animals; classes
have been used to model our main classification of animals, and
interfaces provide support for the management of secondary
classifications. We first demonstrate a case in which the types and
constructors are defined on the C++ side and imported from the Ada
side, and latter the reverse case.

The root of our derivation will be the Animal class, with a
single private attribute (the Age of the animal), a constructor,
and two public primitives to set and get the value of this attribute.

Abstract interface types are defined in C++ by means of classes with pure
virtual functions and no data members. In our example we will use two
interfaces that provide support for the common management of Carnivore
and Domestic animals:

In the following examples we will assume that the previous declarations are
located in a file named animals.h. The following package demonstrates
how to import these C++ declarations from the Ada side:

Thanks to the compatibility between GNAT run-time structures and the C++ ABI,
interfacing with these C++ classes is easy. The only requirement is that all
the primitives and components must be declared exactly in the same order in
the two languages.

Regarding the abstract interfaces, we must indicate to the GNAT compiler by
means of a pragma Convention (C_Plus_Plus), the convention used to pass
the arguments to the called primitives will be the same as for C++. For the
imported classes we use pragma Import with convention C_Plus_Plus
to indicate that they have been defined on the C++ side; this is required
because the dispatch table associated with these tagged types will be built
in the C++ side and therefore will not contain the predefined Ada primitives
which Ada would otherwise expect.

As the reader can see there is no need to indicate the C++ mangled names
associated with each subprogram because it is assumed that all the calls to
these primitives will be dispatching calls. The only exception is the
constructor, which must be registered with the compiler by means of
pragma CPP_Constructor and needs to provide its associated C++
mangled name because the Ada compiler generates direct calls to it.

With the above packages we can now declare objects of type Dog on the Ada side
and dispatch calls to the corresponding subprograms on the C++ side. We can
also extend the tagged type Dog with further fields and primitives, and
override some of its C++ primitives on the Ada side. For example, here we have
a type derivation defined on the Ada side that inherits all the dispatching
primitives of the ancestor from the C++ side.

It is important to note that, because of the ABI compatibility, the programmer
does not need to add any further information to indicate either the object
layout or the dispatch table entry associated with each dispatching operation.

Now let us define all the types and constructors on the Ada side and export
them to C++, using the same hierarchy of our previous example:

Compared with our previous example the only differences are the use of
pragma Convention (instead of pragma Import), and the use of
pragma Export to indicate to the GNAT compiler that the primitives will
be available to C++. Thanks to the ABI compatibility, on the C++ side there is
nothing else to be done; as explained above, the only requirement is that all
the primitives and components are declared in exactly the same order.

For completeness, let us see a brief C++ main program that uses the
declarations available in animals.h (presented in our first example) to
import and use the declarations from the Ada side, properly initializing and
finalizing the Ada run-time system along the way: