C++ and Ada speacking to each others
As of this writting, there is no standardized way to interface C++ and Ada.
The GNAT compiler offers some pragmas such as CPP_Class,
but they are compiler-dependant and quite uneasy to use (requiring to give the
C++ mangled name, for example _ZN6QColor6setRgbEiiii for the method
QColor::setRgb()). So those facilities are not used. Anyway, they
would not allow access to inlined methods.
Hence the only way to communicate between C++ and Ada is to use an intermediate
C binding: each C++ method is translated into an extern "C" function,
which can then be Imported into Ada.
This intermediate C binding is what we call the C/C++ part of . It is a set of C proxies to C++ classes, one proxy for each class. All of them are located into the directory proxies in the source code.
C++ classes and Ada types
Various ways are used to link C++ classes and Ada types. The C++ classes can be divided in two main flavours:
- classes storing only basic data types (such as
ints) and having no virtual methods; - classes storing pointers, allocating memory or having virtual methods.
Basic Qt classes such as QPoint, QSize, etc. belong to the
first family. Classes using implicit sharing (like QString) belong
to the second family, as well as any class having or inheriting from a class having a
virtual method (even a virtual destructor). For example, QEvent, QObject
and so on.
Furthermore, the second family contains classes having a value semantic (i.e. instances
can be copied using affectation operator) and others that cannot be copied (such as
QObject and its derivatives).
Simple classes
Those classes are encapsulated into Ada using the type Cpp_Storage, defined
into the package Cpp_Storages (cpp_storages.ads). Because
they contain only simple datas, it is possible to let Ada perform the memory allocation
(on the stack), avoiding the use of slower dynamic allocation. To achieve this, the type
Cpp_Storage is parametrized using the size (in bytes) needed to hold an instance
of the encapsulated class. Cpp_Storage is a child type of
Ada.Finalization.Controlled, thus we can define our own initialisation procedure
(the defaults for Adjust and Finalize are enough for those simple
classes).
As an example, let's consider the binding for the QRect class. First of all,
we shall create to proxy files into proxies, namely
qrect_proxy.h and qrect_proxy.cpp. The header
file looks like this:
#ifndef __QRECT_PROXY_H__ #define __QRECT_PROXY_H__ 1 #include <QRect> extern "C" { size_t Sizeof_QRect = sizeof(QRect); void QRect_QRect(QRect* qc); // ... void QRect_QRect_QPoint_QSize(QRect* qr, QPoint* qp, QSize* qs); // ... int QRect_Contains_QPoint_Int(QRect* qr, QPoint* pt, int proper); // ... } #endif
The first thing to do is to export the needed size for the class's instances: this is done line 6. Then each method is converted to a C function, applying the following rules:
- the function's name is build by concatenating the class's name, the method's name, and the types names of the parameters, those components being separated by an underscore;
- the first parameter of the C function is always a pointer to the class being interfaced;
- if a parameter is to be passed by reference or by pointer, it's always passed by pointer
to the C function, the type name being suffixed by an
"s"(as star); - only pointers and basic C types are allowed: each
boolis passed as anint.
These rules are not just for fun, they ease greatly the later readings of the code and help tracking mistakes. Don't forget all of this is written "by hand", and we're just human beings...
The implementation just calls the appropriate methods, converting parameters as necessary. The
only little subtle thing is the constructor, which uses the placement new:
void QRect_QRect(QRect* qr) { new (qr) QRect(); }
Now we can begin the corresponding Ada package, here Qt.Core.QRects, in files
qt-core-qrects.ads (spec) and qt-core-qrects.adb(body) into the directory src. The specification begins with:
with Cpp_Storages; use Cpp_Storages; with Qt.Core.QPoints; use Qt.Core.QPoints; with Qt.Core.QSizes; use Qt.Core.QSizes; package Qt.Core.QRects is -- class QRect Sizeof_QRect: constant IC.size_t; pragma Import(C, Sizeof_QRect, "Sizeof_QRect"); type QRect is new Cpp_Storage(Sizeof_QRect) with private; -- QRect () overriding procedure Initialize(qr: in out QRect); -- ... -- QRect ( const QPoint & topLeft, const QSize & size ) not overriding function To_QRect(tl: in QPoint'Class; sz: in QSize'Class) return QRect; -- ... -- bool contains ( const QPoint & point, bool proper = false ) const not overriding function Contains(qr : in QRect; qp : in QPoint; proper: in Boolean := False) return Boolean;
First the needed size is imported from the C part (lines 6-7). Then the type QRect
is declared, as a child type of Cpp_Storage given the size. Now each time you declare
an Ada variable of type QRect, the corresponding C++ instance will be created on
the stack, thanks to the procedure Initialize.
The comments are rather mandatory. The first one (line 5) names the class begin ported to Ada. Then before each subprogram the method prototype is given, as can be found into the Qt's documentation. Those comments are used to compute the big table into the Status page.
Now let's have a look at the package's body:
The name IC is defined into the root package Qt and is
just a renaming of Interfaces.C.
package body Qt.Core.QRects is use type IC.int; -- QRect () procedure QRect_QRect(qr: in Cpp_Store); pragma Import(C, QRect_QRect, "QRect_QRect"); -- ... -- QRect ( const QPoint & topLeft, const QSize & size ) procedure QRect_QRect_QPoint_QSize(qr: in Cpp_Store; tl: in Cpp_Store; br: in Cpp_Store); pragma Import(C, QRect_QRect_QPoint_QSize, "QRect_QRect_QPoint_QSize"); -- ... -- bool contains ( const QPoint & point, bool proper = false ) const function QRect_Contains_QPoint_Int(qr : in Cpp_Store; point : in Cpp_Store; proper: in IC.int) return IC.int; pragma Import(C, QRect_Contains_QPoint_Int, "QRect_Contains_QPoint_Int"); -- ... -- QRect () overriding procedure Initialize(qr: in out QRect) is begin Cpp_Storage(qr).Initialize; QRect_QRect(qr.Cpp); end Initialize; -- ... -- QRect ( const QPoint & topLeft, const QSize & size ) not overriding function To_QRect(tl: in QPoint'Class; sz: in QSize'Class) return QRect is qr: QRect; begin QRect_QRect_QPoint_QSize(qr.Cpp, tl.Cpp, sz.Cpp); return qr; end To_QRect; -- ... -- bool contains ( const QPoint & point, bool proper = false ) const not overriding function Contains(qr : in QRect; qp : in QPoint; proper: in Boolean := False) return Boolean is begin if proper then return QRect_Contains_QPoint_Int(qr.Cpp, qp.Cpp, 1) /= 0; else return QRect_Contains_QPoint_Int(qr.Cpp, qp.Cpp, 0) /= 0; end if; end Contains; -- ...
Note how the comments are repeated again and again. This is not strictly mandatory, but for code's clarity they should be repeated this way.
The body begins by importing the needed C functions. Before calling them, Ada types shall be
converted to C-compatible types, using the facilities provided into the standard package
Interfaces.C (again, renamed as IC). The pointer to C++ instances
are obtained using the Cpp function, defined on the base type Cpp_Storage.
Note the call to the Initialize procedure for this type (line 32) into the
overriden procedure for QRect: this is a real must. If you forget it, everything is
very likely to crash.



