I'm working on an Ada language binding for a C library and stumbled upon a function which expects an array of union values. I tried to used Unchecked_Union
aspect without defining discriminant record but it didn't work because Ada doesn't accept unconstrained element type in array.
In C, function and union are declared something like this:
union argument {
int32_t i;
uint32_t u;
fixed_t f; // custom 24.8 floating point type, defined as int32_t
const char *s; // string
struct object *o; // custom object type
uint32_t n; // may be set as id from struct object (o->id)
struct array *a; // custom array type
int32_t h; // file descriptor
};
.. foo(.., union argument *args);
I'm using GNAT toolchain and running gcc
with -fdump-ada-spec
produced type:
type argument (discr : unsigned := 0) is record
case discr is
when 0 =>
i : aliased x86_64_linux_gnu_bits_stdint_intn_h.int32_t;
when 1 =>
u : aliased x86_64_linux_gnu_bits_stdint_uintn_h.uint32_t;
when 2 =>
f : aliased fixed_t;
when 3 =>
s : Interfaces.C.Strings.chars_ptr;
when 4 =>
o : access object;
when 5 =>
n : aliased x86_64_linux_gnu_bits_stdint_uintn_h.uint32_t;
when 6 =>
a : access array;
when others =>
h : aliased x86_64_linux_gnu_bits_stdint_intn_h.int32_t;
end case;
end record
with Convention => C_Pass_By_Copy,
Unchecked_Union => True;
I replaced unsigned discriminant with enum type and it works fine when I use it as a single value, or as an array of unchecked unions with same discriminant value, but I can't figure out the proper way of using different union components in Ada. I do have some ideas to workaround this though, but I'm not sure if they are correct or possible to implement in Ada.
Observations:
Option 1 Use varargs version and generate in Ada several overloaded functions with different count/type combinations. IIUC, this will require 20 * 8 function definitions, not fun at all.
Option 2
Write an import function with definite union type and then somehow cast with Unchecked_Conversion to/from values, i.e. array (Integer range 0..19) of argument(4)
, then convert elements of an array to different types.
Option 3
Take advantage of max array size and allocate (aliased) memory blob of 20 * 64 bytes (union size), write helper procedures which will read/write correct values at correct memory locations, and then pass this blob either as 'Access
or 'Address
to the C function. In this case function parameter would be access my_blob
or just System.Address
.
I'm personally leaning towards option 3, but it'll require a considerable amount of research/work/testing so I'm asking if there are better ways to do that.
P.S. I think it is a defect on Ada side as ARM B.3.3 §14/2 clearly states that "All objects of an unchecked union type have the same size", so it should be possible to create an array of unchecked unions without defining discriminant. But I understand that it was done to make code safer to use.
If your record type has a default discriminant, then you should be able to declare an array with it as the component type. But it's usually better to do things right on the Ada side and ignore how C is dealing with things as much as possible.
C is expecting an array of 32-bit values, which it will then somehow decide how to interpret, so that's what you should give it.
type U32_C is new Interfaces.Unsigned_32 with Convention => C;
subtype Natural_C is Interfaces.C.int range 0 .. Interfaces.C.int'Last;
type Argument_List is array (Natural_C range <>) of U32_C with
Convention => C;
function Foo (...; Argument : in out Argument_List; ...) ... with
Import, Convention => C, Link_Name => ...;
You then unchecked-convert your 32-bit values to U32_C to create the array to pass.