14
votes

Using GNAT Ada and Gnu C++, I'm interfacing an Ada piece of code with a c++ wrapper and I'd like to catch Ada exceptions properly when running this (stupid) code:

with ada.text_io;

package body ada_throw is

   procedure ada_throw is
   begin
      ada.text_io.put_line ("hello");
      raise program_error;
   end ada_throw;       

end ada_throw;

relevant spec code is:

package ada_throw is

   procedure ada_throw;
   pragma export (convention => C, entity => ada_throw, external_name => "ada_throw");

end ada_throw;

Whe doing this on the C++ side:

#include <iostream>

extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();
  ada_throw();
  std::cout << "end of program" << std::endl;
  return 0;
}

I'm getting this:

hello

raised PROGRAM_ERROR : ada_throw.adb:8 explicit raise

So the exception mechanism works, I don't get the last print of my C++ program and the return code is non-zero.

Now I want to catch the exception. If I use catch(...) it works, but I can't get the explicit error message anymore, so I tried this:

#include <iostream>
#include <cxxabi.h>
extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();

  try
  {
    ada_throw();
  }
  catch (abi::__foreign_exception const &e)
  {
    std::cout << "exception" << std::endl;        
  }

  std::cout << "end of program" << std::endl;
  return 0;
}

it works properly, I get:

hello
exception
end of program

The only catch is that abi::__foreign_exception doesn't have a what() method, so I cannot get hold of the meaningful error message.

And debugging the program to try to hack into e is also a dead end since it's just a null pointer with the proper type:

(gdb) p &e
$2 = (const __cxxabiv1::__foreign_exception *) 0x0

Is there a way to get it from the C++ ?

2
This really depends on how your respective ada and c++ compilers implement exceptions. Both languages specify things differently, and it takes deliberate design choices - in both the compilers code-generation, and in use of library features - to ensure compatibility or, at least, a consistent mapping across the boundary between languages. Any solution will be specific to particular sets of compilers. - Peter
Yes, this question is tagged gnat as well. So it's using g++/gnat, I'll edit - Jean-François Fabre
The world of ABI is only very slowly evolving beyond a C interface. Exceptions are a compiler specific thing, although there might be a platform convention to which they adhere. - Khouri Giordano
Also found this : function Get_Exception_Machine_Occurrence (X : Exception_Occurrence) return System.Address; pragma Export (Ada, Get_Exception_Machine_Occurrence,"__gnat_get_exception_machine_occurrence"); -- Get the machine occurrence corresponding to an exception occurrence. It is Null_Address if there is no machine occurrence (in runtimes tha doesn't use GCC mechanism) or if it has been lost (Save_Occurrence doesn't save the machine occurrence). - LoneWanderer
I was talking with AdaCore GNAT maintainers and they have a lead for that though: catch the exception, then invoke some GNAT.Last_Exception (don't remember exactly) to get the last thrown exception. Not perfect in multithreaded environment, but would work most of the time (unfortunately the feature is broken, and being fixed at the current time, only for beta releases of the GNATPro edition, so not available to the public / GPL until 2019) - Jean-François Fabre

2 Answers

3
votes

In Ada you can get information about an exception occurrence using the functions Ada.Exceptions.Exception_Name and Ada.Exceptions.Exception_Message, which are a part of the standard library. One option is to provide a thin binding to these two functions, and call them, when you need information about the exception. (Exactly how to map abi::__foreign_exception to Ada.Exceptions.Exception_ID is left as an exercise for the reader.)

Another option is to make it explicit to the Ada compiler that you are writing C++ on the other side, and use CPlusPlus instead of C as your export convention. That may make the compiler provide exceptions, which C++ understands.

This is only a partial answer, as I'm very much out of practice with C++, but I hope it can help you in the right direction.

3
votes

Depending on what you want to do with the exception in the C++ world, it might be easier/cleaner to write a wrapper in Ada, which calls ada_throw and handles any exception. Then simply call this wrapper from C++.

If the C++ code must see the exception and know what's going on, the wrapper could provide a what() method lookalike, and re-raise either the same or a new (custom) exception.