Interfacing with C and C++
Interfacing with C
Using unconstrained types
In the previous examples, we're being careful about the data types: all of
them are coming from the Interfaces.C
package. Using Ada built-in
types when interfacing with C can be problematic, especially in case of
unconstrained types. For example:
/*% filename: test.h */
char * my_func (void);
This is the function implementation:
#include <stdio.h>
#include "test.h"
char * my_func (void)
{
return "hello";
}
In the Ada application, we try to import this as a String
type:
with Interfaces.C;
use Interfaces.C;
with Ada.Text_IO;
use Ada.Text_IO;
procedure Show_C_Func is
function my_func return String
with
Import => True,
Convention => C;
S : String := my_func;
begin
Put_Line (S);
end Show_C_Func;
When running this application, we'll get a Storage_Error
exception.
Therefore, the recommendation is to be very careful about the data types
and use the Interfaces.C
package whenever possible for interfacing
with C.
Interfacing with C++
All the previous examples focused on interfacing with C code. For C++, the same methods apply. However, there are a few differences that we need to take into account:
When importing or exporting variables and subprograms, we replace 'C' by 'Cpp' in the
Convention
aspect of their declaration.In the project file for gprbuild, we replace 'C' by 'C++' in the
Languages
entry.
There are other aspects specific to C++ that we also have to take into account. This section will discuss them.
C++ symbol mangling
Let's start by adapting a previous example and converting it to C++ (actually, mainly just replacing the C compiler by a C++ compiler). The header file is still basically the same:
extern int func_cnt;
int my_func (int a);
And this is the corresponding implementation:
#include "test.hh"
int func_cnt = 0;
int my_func (int a)
{
func_cnt++;
return a * 2;
}
In the Ada application, as mentioned before, we need to replace 'C' by
'Cpp' in the Convention
of the declarations:
with Interfaces.C; use Interfaces.C;
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Cpp_Func is
function my_func (a : int) return int
with
Import => True,
Convention => Cpp,
External_Name => "_Z7my_funci";
V : int;
func_cnt : int
with
Import => True,
Convention => Cpp;
begin
V := my_func (1);
V := my_func (2);
V := my_func (3);
Put_Line ("Result is "
& int'Image (V));
Put_Line ("Function was called "
& int'Image (func_cnt)
& " times");
end Show_Cpp_Func;
Also, in the declaration of my_func
, we need to include a reference to
the original name using External_Name
. If we leave this out, the
linker won't be able to find the original implementation of my_func
,
so it won't build the application. Note that the function name is not
my_func
anymore (as it was the case for the C version). Instead, it is
now called _Z7my_funci
. This situation is caused by symbol mangling.
In C, the symbol names in object files match the symbol name in the source-code. In C++, due to symbol mangling, the symbol names of subprograms in the object files are different from the corresponding source-code implementation. Also, because symbol mangling is not standardized, different compilers might use different methods. The most prominent example is the difference between the gcc and MSVC compilers. However, since GNAT is based on gcc, we can build applications using Ada and C++ code without issues — as long as we use the same compiler.
In order to retrieved the mangled symbol names, we can simply generate
bindings automatically by using g++ with the -fdump-ada-spec
option:
g++ -c -fdump-ada-spec -C ./test.hh
Alternatively, we could use binary examination tools to retrieve the symbol names from a library. Examples of such tools are nm for Mac and Linux, and dumpbin.exe for Windows.
C++ classes
We'll now focus on binding object-oriented features of C++ into Ada. Let's adapt the previous example to make use of classes. This is adapted header file:
class Test {
public:
Test();
int my_func (int a);
int get_cnt();
private:
int func_cnt;
};
And this is the corresponding implementation:
#include "test.hh"
Test::Test() :
func_cnt(0)
{
};
int
Test::my_func (int a)
{
func_cnt++;
return a * 2;
}
int
Test::get_cnt()
{
return func_cnt;
}
Because of the more complex structure, the recommendation is to generate bindings using g++ and, if needed, adapt the file. Let's first run g++:
g++ -c -fdump-ada-spec -C ./test.hh
The generated bindings look like this:
pragma Ada_2005;
pragma Style_Checks (Off);
with Interfaces.C; use Interfaces.C;
package test_hh is
package Class_Test is
type Test is limited record
func_cnt : aliased int; -- ./test.hh:7
end record;
pragma Import (CPP, Test);
function New_Test return Test; -- ./test.hh:3
pragma CPP_Constructor (New_Test, "_ZN4TestC1Ev");
function my_func (this : access Test; a : int) return int; -- ./test.hh:4
pragma Import (CPP, my_func, "_ZN4Test7my_funcEi");
function get_cnt (this : access Test) return int; -- ./test.hh:5
pragma Import (CPP, get_cnt, "_ZN4Test7get_cntEv");
end;
use Class_Test;
end test_hh;
As we can see, the original C++ class (Test
) is represented as a
nested package (test_hh.Class_Test
) in the Ada bindings.
The Ada application can then use the bindings:
with Interfaces.C; use Interfaces.C;
with Ada.Text_IO; use Ada.Text_IO;
with test_hh; use test_hh;
procedure Show_Cpp_Class is
use Class_Test;
V : int;
T : aliased Test := New_Test;
TA : access Test := T'Access;
begin
V := my_func (TA, 1);
V := my_func (TA, 2);
V := my_func (TA, 3);
Put_Line ("Result is " & int'Image (V));
Put_Line ("Function was called "
& int'Image (get_cnt (TA))
& " times");
end Show_Cpp_Class;
Note that, in the Ada application, we cannot use the prefixed notation.
This notation would be more similar to the corresponding syntax in C++.
This restriction is caused by the fact that the automatic generated
bindings don't use tagged types. However, if we adapt the declaration of
Test
and replace it by type Test is tagged limited record ...
,
we'll be able to write TA.my_func(1)
and TA.get_cnt
in our
application.
Another correction we might want to make is in the visibility of the
Test
record. In the original C++ class, the func_cnt
element was
declared in the private part of the Test
class. However, in the
generated bindings, this element has been exposed, so it could be accessed
directly in our application. In order to correct that, we can simply move
the type declaration to the private part of the Class_Test
package and
indicate that in the public part of the package (by using
type Test is limited private;
).
After these adaptations, we get the following bindings:
pragma Ada_2005;
pragma Style_Checks (Off);
with Interfaces.C; use Interfaces.C;
package test_hh is
package Class_Test is
type Test is tagged limited private;
pragma Import (CPP, Test);
function New_Test return Test; -- ./test.hh:3
pragma CPP_Constructor (New_Test, "_ZN4TestC1Ev");
function my_func (this : access Test; a : int) return int; -- ./test.hh:4
pragma Import (CPP, my_func, "_ZN4Test7my_funcEi");
function get_cnt (this : access Test) return int; -- ./test.hh:5
pragma Import (CPP, get_cnt, "_ZN4Test7get_cntEv");
private
type Test is tagged limited record
func_cnt : aliased int; -- ./test.hh:7
end record;
end;
use Class_Test;
end test_hh;
And this is the adapted Ada application:
with Interfaces.C; use Interfaces.C;
with Ada.Text_IO; use Ada.Text_IO;
with test_hh; use test_hh;
procedure Show_Cpp_Class is
use Class_Test;
V : int;
T : aliased Test := New_Test;
TA : access Test := T'Access;
begin
V := TA.my_func (1);
V := TA.my_func (2);
V := TA.my_func (3);
Put_Line ("Result is "
& int'Image (V));
Put_Line ("Function was called "
& int'Image (TA.get_cnt)
& " times");
end Show_Cpp_Class;
C++ constructors
Note
This section was originally written by Javier Miranda and Arnaud Charlet, and published as Gem #61: Interfacing with C++ constructors and Gem #62: C++ constructors and Ada 2005
Todo
Complete section!