Ada's Dirty Pointer Secrets: Passing Dynamically Allocated Arrays from Ada to C



March 6th, 2021 by Diana Coman

In a rather prissy manner and for all its significant advantages otherwise, Ada has this very annoying habit of glossing over and leaving *silently* to others - most usually and for historical reasons to C - essential bits and parts that are needed but don't quite fit all that easily into the clean and tidy worldview that Ada itself can strictly check and enforce. As a result, the whole "program in Ada" pleasant environment holds only if you don't look too deep into it and for as long as you steer away from any real world tasks that tend to include very soon such messy things as interacting with databases1, or working *fast* with large data structures that you really don't afford to handle daintily, one element at a time, without pointers and the like. As soon as you venture into such things, programming in Ada becomes instead very quickly an exercise in "interface with C" as far as I can describe it and moreover, you are pretty much left to figure things out on your own, presumably because it's exactly when things get difficult that you truly want to find yourself suddenly on your own and without any guarantees, isn't it?

Anyways, long story short, Ada deigns at least to provide a thin layer of bindings for the C types, some sort of pointers ("access" types), the 'Address attribute and even a package for converting between address and access (not that it makes very clear, explicitly, the difference between the two - why write it if you can figure it out yourself anyway) as well as another package for pointer arithmetic if you really are the sort of low life to do such dirty things. Based on those and on my (exasperatingly) recurring need of interfacing with C code, I ended up essentially exploring different ways2 to pass dynamically allocated arrays from one side to another and even back, while at the same time just learning Ada as well, since reality tends to be what it is rather than what would be recommended or even reasonable. Nevertheless, I finally got to the point where I'd rather document at least *one* working way of passing dynamically allocated arrays from Ada to C and have it therefore as some sort of reference of my own (since there's no other type of reference I can find anywhere on the topic) in one place for all those future times when I'll run yet again into this same problem because by now it's quite clear enough to me that it will be needed again for sure.

To transfer basically Ada-allocated pointers to C, the core of it all is to pack Ada's own pointer so that even its otherwise hidden additional information is taken into account - this allows direct use then on the C side3. On Ada side, there are first a bunch of declarations for the desired element type (in this case the most basic type possible, namely one raw, unsigned byte - technically this would be unsigned char in C), an unconstrained (ie any size) array of that type, a pointer to such an array and then the record that groups together the pointer and the array's actual size:

with Interfaces;
with Interfaces.C;
with Ada.Unchecked_Deallocation;

package AdaC is
--  type u8 is mod 256;
  subtype u8 is Interfaces.Unsigned_8;
--  pragma Convention(C, u8);

  type u8_array is array(Interfaces.C.size_t range <>) of u8;
  pragma Convention(C, u8_array);

  type array_pointer is access all u8_array;

  type u8_buffer is
    record
      data: array_pointer;
      size: Interfaces.C.size_t;
    end record;
  pragma Convention(C_Pass_By_Copy, u8_buffer);

In the above, I preferred to use the library type Interfaces.Unsigned_8, mainly because it is the type that I am using otherwise throughout a whole lot of useful code so I'd really rather *not* have to convert anything (having to convert each element of an array or something kind of defeats the purpose of the whole exercise in the first place). The pragma Convention C is supposedly the default convention for GNAT anyway but I'd rather have it explicit where possible (for my u8 type it's commented out since I define u8 as a subtype of an already existing type). The pragma convention c_pass_by_copy does what it says. The next missing bit on Ada side is to define as well a way to deallocate the memory allocated (the prissy way says to not use this dirty unchecked_deallocation since the pointer will simply end its life when out of scope; the glossed over part is that there is no guarantee that the memory used up is recovered and re-used as well - so in principle, you could end up out of memory simply because... it never gets reused) and then to import the C function that is to use this Ada-allocated array:

  procedure Free is new Ada.Unchecked_Deallocation(u8_array, array_pointer);

  function Func_C( ptr: access u8_buffer)
        return Interfaces.C.int;
  pragma Import(C, Func_C, "func_c");

end AdaC;

On the C side, the equivalent structure (struct) has to have one additional pointer member that is basically a placeholder - because Ada passes with the access type some additional data that C has no idea about, one has to...figure that out, shake one's head and then pad the structure to account for it, as disgusting as that might be:

struct u8_buffer {
  unsigned char *data;
  void *placeholder;
  size_t size;
};

int func_c( const struct u8_buffer *d);

For the basic testing part, the above C function goes through the array's elements, printing them in turn and returning their sum. The basic testing on Ada part allocates the array and initializes it, calls the C function and prints it all as well, to admire just how well the prissy can get along with the dirty workhorse, despite claiming that they have nothing in common at all:

int func_c( const struct u8_buffer *d) {
  size_t i;
  int sum;
  sum = 0;
  printf("From func_c in C, the buffer has size %zu and elements:\n", d->size);
  for (i=0; i< d->size; i++) {
    printf("%d ", d->data[i]);
    sum = sum + d->data[i];
  }
  printf("\nFunc_C is done.\n");
  return sum;
}

with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C; use Interfaces.C;
with Interfaces; use Interfaces;

package body AdaC is
  procedure Test is
    arr : u8_array := (3,14,29,255,8);
    buff: aliased u8_buffer;
    res : Interfaces.C.int;
  begin
    Put_Line("u8_array in ada has size " & size_t'Image(arr'Length));
    Put_Line("u8_array in ada has elements:");
    for i in arr'Range loop
      Put(u8'Image(arr(i)));
    end loop;
    New_Line;
    -- now set it in buff and pass it to func_C
    buff.size := arr'Length;
    buff.data := new u8_array'(arr);
    -- alternatively, initializing with null and then setting the value:
    --buff.data := new u8_array(arr'First..arr'Last);
    --buff.data.all := arr;
    Put_Line("Ada calling C...");
    res := Func_C( buff'access );
    Put_Line("Ada got result " & int'Image(res));

    -- test 2, change some values
    buff.data.all(2..3) := (1,2);
    res := Func_C( buff'access );
    Put_Line("Ada got result " & int'Image(res));

    Free(buff.data);
  end Test;

end AdaC;

  1. This includes filesystems and for the record, the GNAT implementation of sequential-io relies in the end still on C functions, have a look sometime and follow the gnarled thread that starts in adainclude/a-sequio.ads (aka package Ada.Sequential_IO) and goes all the way to s-crtl.ads aka System.CRTL aka simply a collection of imported C functions (either GCC or C library) from strlen and atoi to fclose, fseek, read and write. Now how much Ada is then this Ada.Sequential_IO implementation? 

  2. One of the earliest attempts with char_array is published here

  3. On a side note and just like with a lot of other things, doing this is an exercise in frustration when you have no idea about it and otherwise it becomes suddenly very easy indeed *when you know what you are doing*. So next time you are lucky enough to find someone who knows what they are doing, maybe save yourself a lot of frustration and just *work with them*? 

Comments feed: RSS 2.0

One Response to “Ada's Dirty Pointer Secrets: Passing Dynamically Allocated Arrays from Ada to C”

  1. [...] ~This seems to turn into a mini-series of sorts, see the previous example of passing dynamically allocated arrays from Ada to C~ [...]

Leave a Reply