While writing bindings for interfacing with C code, I am finding issues translating the numerous instances of structs with flexible array members to Ada, as such
struct example {
size_t length;
int body[];
};
I've been told for Ada that behaviors like that can be replicated with discriminated types, but I cannot find a way to use the length
field as a discriminant, while maintaining the layout of the structure so that the records can be used to interface with the C code, something like
type Example (Count : Integer := Length) is record
Length : Unsigned_64;
Body : Integer (1 .. Count);
end record;
Is there any way to create a type like that with that array? I've been defaulting for now to grabbing the address of that location and declaring the array myself on place for use, is there any better way? Thanks in advance.
Here is an example in which Ada code calls C code, passing objects of this kind from Ada to C and from C to Ada. The C header is c_example.h:
typedef struct {
size_t length;
int body[];
} example_t;
extern void example_print (example_t *e /* in */);
// Print the contents of e.
extern example_t * example_get (void);
// Return a pointer to an example_t that remains owned
// by the C part (that is, the caller can use it, but should
// not attempt to deallocate it).
The C code is c_example.c:
#include <stdio.h>
#include "c_example.h"
void example_print (example_t *e /* in */)
{
printf ("C example: length = %zd\n", e->length);
for (int i = 0; i < e->length; i++)
{
printf ("body[ %d ] = %d\n", i, e->body[i]);
}
} // example_print
static example_t datum = {4,{6,7,8,9}};
example_t * example_get (void)
{
return &datum;
} // example_get
The C-to-Ada binding is defined in c_binding.ads:
with Interfaces.C;
package C_Binding
is
pragma Linker_Options ("c_example.o");
use Interfaces.C;
type Int_Array is array (size_t range <>) of int
with Convention => C;
type Example_t (Length : size_t) is record
Bod : Int_Array(1 .. Length);
end record
with Convention => C;
type Example_ptr is access all Example_t
with Convention => C;
procedure Print (e : in Example_t)
with Import, Convention => C, External_Name => "example_print";
function Get return Example_Ptr
with Import, Convention => C, External_Name => "example_get";
end C_Binding;
The test main program is flexarr.adb:
with Ada.Text_IO;
with C_Binding;
procedure FlexArr
is
for_c : constant C_Binding.Example_t :=
(Length => 5, Bod => (55, 66, 77, 88, 99));
from_c : C_Binding.Example_ptr;
begin
C_Binding.Print (for_c);
from_c := C_Binding.Get;
Ada.Text_IO.Put_Line (
"Ada example: length =" & from_c.Length'Image);
for I in 1 .. from_c.Length loop
Ada.Text_IO.Put_Line (
"body[" & I'Image & " ] =" & from_c.Bod(I)'Image);
end loop;
end FlexArr;
I build the program thusly:
gcc -c -Wall c_example.c
gnatmake -Wall flexarr.adb
And this is the output from ./flexarr:
C example: length = 5
body[ 0 ] = 55
body[ 1 ] = 66
body[ 2 ] = 77
body[ 3 ] = 88
body[ 4 ] = 99
Ada example: length = 4
body[ 1 ] = 6
body[ 2 ] = 7
body[ 3 ] = 8
body[ 4 ] = 9
So it seems to work. However, the Ada compiler (gnat) gives me some warnings from the C_Binding package:
c_binding.ads:14:12: warning: discriminated record has no direct equivalent in C
c_binding.ads:14:12: warning: use of convention for type "Example_t" is dubious
This means that while this interfacing method works with gnat, it might not work with other compilers, for example Ada compilers that allocate the Bod component array separately from the fixed-size part of the record (whether such a compiler would accept Convention => C for this record type is questionable).
To make the interface more portable, make the following changes. In C_Binding, change Length from a discriminant to an ordinary component and make Bod a fixed-size array, using some maximum size, here 1000 elements as an example:
type Int_Array is array (size_t range 1 .. 1_000) of int
with Convention => C;
type Example_t is record
Length : size_t;
Bod : Int_Array;
end record
with Convention => C;
In the test main program, change the declaration of for_c to pad the array with zeros:
for_c : constant C_Binding.Example_t :=
(Length => 5, Bod => (55, 66, 77, 88, 99, others => 0));
For speed, you could instead let the unused part of the array be uninitialized: "others => <>".
If you cannot find a reasonably small maximum size, it should be possible to define the C binding to be generic in the actual size. But that is getting rather messy.
Note that if all the record/struct objects are created on the C side, and the Ada side only reads and writes them, then the maximum size defined in the binding is used only for index bounds checking and can be very large without impact on the memory usage.
In this example I made the Ada side start indexing from 1, but you can change it to start from zero if you want to make it more similar to the C code.
Finally, in the non-discriminated case, I recommend making Example_t a "limited" type ("type Example_t is limited record ...") so that you cannot assign whole values of that type, nor compare them. The reason is that when the C side provides an Example_t object to the Ada side, the actual size of the object may be smaller than the maximum size defined on the Ada side, but an Ada assignment or comparison would try to use the maximum size, which could make the program read or write memory that should not be read or written.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With