Qt4Ada - An Ada2005 binding to Qt4

  1. C++ and Ada speacking to each others
  2. C++ classes and Ada types
  3. Simple classes
  4. Complex classes

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:

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:

  1. #ifndef __QRECT_PROXY_H__
  2. #define __QRECT_PROXY_H__ 1
  3. #include <QRect>
  4. extern "C"
  5. {
  6. size_t Sizeof_QRect = sizeof(QRect);
  7. void QRect_QRect(QRect* qc);
  8. // ...
  9. void QRect_QRect_QPoint_QSize(QRect* qr,
  10. QPoint* qp,
  11. QSize* qs);
  12. // ...
  13. int QRect_Contains_QPoint_Int(QRect* qr,
  14. QPoint* pt,
  15. int proper);
  16. // ...
  17. }
  18. #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:

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:

  1. void QRect_QRect(QRect* qr)
  2. {
  3. new (qr) QRect();
  4. }

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:

  1. with Cpp_Storages; use Cpp_Storages;
  2. with Qt.Core.QPoints; use Qt.Core.QPoints;
  3. with Qt.Core.QSizes; use Qt.Core.QSizes;
  4. package Qt.Core.QRects is
  5. -- class QRect
  6. Sizeof_QRect: constant IC.size_t;
  7. pragma Import(C, Sizeof_QRect, "Sizeof_QRect");
  8. type QRect is new Cpp_Storage(Sizeof_QRect) with private;
  9. -- QRect ()
  10. overriding
  11. procedure Initialize(qr: in out QRect);
  12. -- ...
  13. -- QRect ( const QPoint & topLeft, const QSize & size )
  14. not overriding
  15. function To_QRect(tl: in QPoint'Class;
  16. sz: in QSize'Class)
  17. return QRect;
  18. -- ...
  19. -- bool contains ( const QPoint & point, bool proper = false ) const
  20. not overriding
  21. function Contains(qr : in QRect;
  22. qp : in QPoint;
  23. proper: in Boolean := False)
  24. 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.

  1. package body Qt.Core.QRects is
  3. use type IC.int;
  5. -- QRect ()
  6. procedure QRect_QRect(qr: in Cpp_Store);
  7. pragma Import(C,
  8. QRect_QRect,
  9. "QRect_QRect");
  10. -- ...
  11. -- QRect ( const QPoint & topLeft, const QSize & size )
  12. procedure QRect_QRect_QPoint_QSize(qr: in Cpp_Store;
  13. tl: in Cpp_Store;
  14. br: in Cpp_Store);
  15. pragma Import(C,
  16. QRect_QRect_QPoint_QSize,
  17. "QRect_QRect_QPoint_QSize");
  18. -- ...
  19. -- bool contains ( const QPoint & point, bool proper = false ) const
  20. function QRect_Contains_QPoint_Int(qr : in Cpp_Store;
  21. point : in Cpp_Store;
  22. proper: in IC.int)
  23. return IC.int;
  24. pragma Import(C,
  25. QRect_Contains_QPoint_Int,
  26. "QRect_Contains_QPoint_Int");
  27. -- ...
  28. -- QRect ()
  29. overriding
  30. procedure Initialize(qr: in out QRect) is
  31. begin
  32. Cpp_Storage(qr).Initialize;
  33. QRect_QRect(qr.Cpp);
  34. end Initialize;
  35. -- ...
  36. -- QRect ( const QPoint & topLeft, const QSize & size )
  37. not overriding
  38. function To_QRect(tl: in QPoint'Class;
  39. sz: in QSize'Class)
  40. return QRect is
  41. qr: QRect;
  42. begin
  43. QRect_QRect_QPoint_QSize(qr.Cpp, tl.Cpp, sz.Cpp);
  44. return qr;
  45. end To_QRect;
  46. -- ...
  47. -- bool contains ( const QPoint & point, bool proper = false ) const
  48. not overriding
  49. function Contains(qr : in QRect;
  50. qp : in QPoint;
  51. proper: in Boolean := False)
  52. return Boolean is
  53. begin
  54. if proper
  55. then
  56. return QRect_Contains_QPoint_Int(qr.Cpp,
  57. qp.Cpp,
  58. 1) /= 0;
  59. else
  60. return QRect_Contains_QPoint_Int(qr.Cpp,
  61. qp.Cpp,
  62. 0) /= 0;
  63. end if;
  64. end Contains;
  65. -- ...

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.

Complex classes