Gem #116: Ada and C++ Exceptions
by Quentin Ochem —AdaCore
Let’s get started…
So we’ve decided to have some fun and are building an application that combines Ada and C++. So far, so good. We used the C++ to Ada binding generator to bind the C++ classes directly to Ada, and extend them. However, there’s something that needs to be considered carefully –- what would happen if an exception were thrown/raised by C++ vs. Ada code? Let’s try it out:
Step 1 — The C++ code
We’re going to write some very simple C++ code, just two classes, one inheriting from the other, and overriding a “compute” primitive:
class COperation { public: virtual int compute (int a, int b); COperation (); }; class CDivision : public COperation { public: virtual int compute (int a, int b); CDivision (); }; int CDivision::compute (int a, int b) { if (b == 0) { throw new Problem ("Division by 0 in C++!"); } else { return a / b; } }
In the computation of CDivision, we’ll raise a C++ exception if a divide-by-zero occurs. Let’s add a second subprogram doing a dynamic dispatch:
void cpp_main (COperation & op) { try { cout << op.compute (1, 0); } catch (...) { cout << "Unknown exception caught, rethrowing..." << " "; throw; } }
This function calls the compute operation, catches the exception to display a message, and then rethrows it.
We’re now going to bind this to Ada. Assuming all specifications are in a file base.hh, a simple call to gcc will do it:
g++ -fdump-ada-spec base.hh
Step 2 — Extending the C++ code in Ada
There is an implementation of CDivision in C++. After the binding phase, let’s implement the same code in Ada:
type Ada_Division is new base_hh.Class_COperation.COperation with null record; function Compute (this : access Ada_Division; a : int; b : int) return int is begin if b = 0 then raise Constraint_Error with "Division by 0 in Ada!"; end if; return a / b; end Compute;
We now have three classes in the application. One root C++ class, one pure C++ child, and one mixed Ada/C++ child. Let’s see how things how things work from there.
Step 3 — Catching a C++ exception in Ada
C++ exceptions do not have a known name once they reach the Ada world. They act just as if they were declared in the body of a package, so the only way to catch them is to use a general exception handler. Consider the following code:
T_Cpp : aliased base_hh.Class_CDivision.CDivision; X : Interfaces.C.Int; begin X := T_Cpp.compute (1, 0); exception when others => Put_Line ("[1] Exception caught..."); end;
This will print “[1] Exception caught…” on the screen.
Step 4 — Handling exceptions across languages
Now let’s be a little bit more ambitious. We’re going to call the cpp_main function with an object coming from Ada:
T_Ada : aliased Base_Ada.Ada_Division; begin base_hh.cpp_main (T_Ada'Access); exception when Constraint_Error => Put_Line ("[2] Constraint_Error caught..."); when others => Put_Line ("[2] Exception caught..."); end;
Now, looking back at the cpp_main implementation, we call compute with (1, 0) as the arguments. This will trigger an exception from the Ada implementation, which is going to be caught first by the C++ code, printing “Unknown exception caught, rethrowing…” on the screen. The same exception is then rethrown by the C++ side, ending up back in the Ada code above, printing “[2] Constraint_Error caught…”.