4

Passing arrays of structures/records from Ada to C routines is one thing. In this case the memory management is done in Ada. But when interfacing with third party libraries there is often the problem that Memory management is done in the C part.

For example: For the C-structure:

typedef struct _MYREC { int n; char *str; } MYREC; 

the following C-routine allocates Memory and returns a pointer to a MYREC-array with n elements:

MYREC * allocMyrec(int n); 

Problem is that the returned Pointer does not contain size-information which is required for memory-safe computation in Ada.

in Ada I would like to use the corresponding Array-Definition for the (MYREC *) pointer:

type MYREC_Array is array (Int range <>) of aliased MYREC; pragma Convention (C, MYREC_Array); 

How would a corresponding (size-awawre) Ada-Function allocMyrec look like or what would be the right strategy?

B.t.w. for one element it is possible to map the C-pointer to access MYREC . That is what the Gnat-Binding generator does. But this is not helpful.

Hints highly appreciated.

2
  • I'm not clear on what you need. Do you want Ada code that actually calls the C allocMyRec function, or do you want Ada code that just does something similar without calling the C function? Commented Apr 29, 2014 at 14:45
  • I have to use the allocation from the C-library. So I want Ada code that actually calls the C allocMyRec but I want to work with ADA on a MYREC_Array Commented Apr 29, 2014 at 15:00

4 Answers 4

3

I finally got it working using the package Interface.C.Pointers, it is quite easy, here is the code :

with Interfaces.C, Interfaces.C.Pointers ; use Interfaces.C ; with Ada.Text_IO ; -- To Check procedure MYRECTest is package IIO is new Ada.Text_IO.Integer_IO (Int) ; type PChar is access all Char ; type MYREC is record N : Int ; Str : PChar ; end record ; pragma Convention(C, MYREC); DefaultMyrec : MYREC := (0, null) ; type MYREC_Array is array (Int range <>) of aliased MYREC ; pragma Convention(C, MYREC_Array); -- Not sure if useful... -- Here is the trick package PMYREC is new Interfaces.C.Pointers (Int, MYREC, MYREC_Array, DefaultMyrec) ; function AllocMyrec (N : in Int) return PMYREC.Pointer ; pragma Import (C, AllocMyrec, "allocMyrec"); P : PMYREC.Pointer := AllocMyrec(5) ; StartP : PMYREC.Pointer := P ; -- Initial pointer A : MYREC_Array(0..4) := PMYREC.Value(P, 5) ; -- Here is a copy begin for I in A'Range loop -- Real access: IIO.Put(P.all.N) ; P.all.N := P.all.N + 3 ; -- Here you're really accessing the allocated memory, not just a copy PMYREC.Increment(P) ; -- 'Fake' access: IIO.Put(A(I).N) ; A(I).N := A(I).N + 3 ; -- Here you're accessing a copy, so the modification is not made on the allocated memory end loop ; Ada.Text_IO.New_Line ; end MYRECTest ; 

I tested the above code setting the n attribute of all _MYREC elements in the C function to 42 + i and printing it in the Ada body, it worked (I got 42, 43, 44, 45, 46). The C function looked like (for test) :

typedef struct _MYREC { int n ; char *str ; } MYREC ; MYREC * allocMyrec (int n) { MYREC *res = malloc(n * sizeof(MYREC)) ; int i ; for (i = 0 ; i < n ; i++) { res[i].n = 42 + i; } return res ; } 

The DefaultMyrec variable is useless but necessary for the package creation. I assumed you always use the Value function with the length parameter to retrieve value.

More information about the package : http://www.adaic.org/resources/add_content/standards/05rm/html/RM-B-3-2.html

EDIT: The original code was making a copy of the memory pointed by P so if you update anything in the array A it won't be change in the memory allocated. In fact you should directly use the pointer P like shown in the edited code.

EDIT: I used a 'stupid' access all Char for the str attribute of MYREC but you can (and should) use almost the same stuff from Interfaces.C.Pointers if necessary. I tested it but did not want to put it in the answer because it did not add anything to it.

Sign up to request clarification or add additional context in comments.

6 Comments

Many thanks for explaining the solution with the Pointer package. As I see the array is created at compile time and I need to know the size at declaration time. I would like to work on the original C-Array as if it were a MYREC_Array.
@user3485675 You can do the same during execution, there is no restriction. But if you want to use a MYREC_Array, you'll have to look at ajb solution.
So ajb's solution is working fine if I am knowing the number of records the C-routine is creating. But what if the C-routine is knowing this at run-time and telling me via an additional out-parameter the allocated size? One way to handle this would be to create an array with hopefully max. exptected capacity and using the length returned. I am wondering how String constructor is telling Ada how long it is.
@user3485675 Because Ada does not allow pointer (access) to unconstraind array, it will be difficult to get a pointer to the allocated array (you can easily get a copy of the array). You can create 2 C functions : One which allocate and returns the size, and the others which return the allocated array. Then you just have to replace the Size parameter in ajb's solution by something like: Size : Int := AllocateMyrec.
thanks for the hint. If I got you right you suggest that the Size-function in the subtype declaration should allocate memory and return the size and the declaration a:MYREC_Array_Subtype := GetMyrecArray should return the pointer to the array that is then copied into the Ada-Array (here a) since :=is making copies, right? Up to now I was not successful to do this. And this strategy will work only if there is one instance of the Array.
|
0

Sorry, I don't think there's a general solution. If you declare a type that's an access to MYREC_Array, e.g.

type MYREC_Array is array (Int range <>) of aliased MYREC; type MYREC_Array_Access is access all MYREC_Array; 

I think the GNAT compiler will expect that this points to data containing the bounds immediately followed by the data. And you're not going to get the C allocMyrec to leave space for the bounds, unless you have access to the C code and can write a wrapper that does leave space for the bounds. But to do this, you'd have to know exactly how the GNAT compiler works, and the answer will be tied to that particular implementation. If there's some special declaration that would tell GNAT to keep the bounds separate, I'm not aware of it.

Some compilers might be able to handle this; e.g. Irvine Compiler's Ada compiler keeps the bound information as part of the MYREC_Array_Access type, not contiguous with the data. (There are advantages and disadvantages to this approach.) For a compiler like that, you could construct a pointer that has the bounds you want and points to the data returned by allocMyrec. But doing so requires using unchecked operations and is highly implementation-specific.

In some cases you could do something like this:

procedure Do_Some_Work (Size : Integer) is subtype MYREC_Array_Subtype is MYREC_Array (1 .. Size); type MYREC_Array_Access is access all MYREC_Array_Subtype; function allocMyrec (Size : Interfaces.C.int) return MYREC_Array_Subtype; pragma Import (C, allocMyrec); begin ... 

Now, since the bounds are built into the subtype, they don't need to be stored in memory anywhere; so this will work, and any time you refer to an element of the array returned by allocMyrec, the compiler will make sure the index is in the range 1..Size. But you won't be able to use this result outside Do_Some_Work. You won't be able to convert the access object to any other access type defined outside Do_Some_Work. So this is a rather limited solution.

EDIT: I was assuming you wanted an Ada access object that points to the array; if not, then Holt's answer is a good one. Sorry if I misunderstood what you were looking for.

2 Comments

Many thanks for this solution that will work for my problem. I have yet to understand what exactly the all is for? What would be the strategy if I'd get the size of the allocated Array back in a variable: MYREC * allocMyRec(int * size).
@user3485675 The all keyword stands for 'general access', i.e. access to all kind of variables. See en.wikibooks.org/wiki/Ada_Programming/Types/…, but shortly it means that you can do more than with a simple access (like accessing variables on the stack), which is allowed by default with pointer in C.
0

This is another answer for what you asked in the preview answer's comments (was too long and too different from the previous answer to only make an edit).

So your C code looks like:

typedef struct { ... } MYREC ; MYREC *last_allocated ; // Allocate an array of N MYREC and return N. // The allocated array is "stored" in last_allocated. int alloc_myrec (void) { ... } MYREC* get_last_allocated (void) { return last_allocated ; } 

Then your Ada body:

procedure MYREC_Test is type MYREC is record ... end record ; pragma Convention(C, MYREC) ; -- Global and unconstrained array type MYREC_Array is array (Int range <>) of aliased MYREC ; pragma Convention(C, MYREC_Array); begin declare -- Allocate and retrieve the array Size : Int := AllocMyrec ; subtype MYREC_Array_Subtype is MYREC_Array (1 .. Size); type MYREC_Array_Access is access all MYREC_Array_Subtype; function GetAlloc return MYREC_Array_Access; pragma Import (C, GetAlloc, "get_last_alloc"); MyArray : MYREC_Array_Access := GetAlloc ; begin -- Do whatever you want with MyArray end ; end ; 

As I said in the previous comment, it's a bit ugly to work that way. The Interfaces.C.Pointers package is mean to do what you want to do and will be easier to use if you put everything you need in a package.

3 Comments

many thanks for lining this out! Indeed I was able to make it work. To be compliant with an C-array range starting from 0 I had to put 0 .. Size -1. When I pass an indexed record to a C function as a(i)'access and print the address (via "%lx") it seems to be different than the allocated one: 26cbe30vs. 26cbe30244d160 but the record access is correct.
Forget the last comment with the different addresses.
If we leave ADA's path of virtue it is also possible to declare the MYREC_Array_Subtype with a maximal expected Size. This declaration could happen anywhere in the program. We need it only one time. When accessing the array one has to pay attention that no access beyond element size-1 occurs, where size is returned by the C-function. If the ADA array is declared smaller than the actual size program will abort with a CONSTRAINED_ERROR at runtime. If the array is larger we will get classic overwriter's known from C and random results bringing the dark side of C into ADA.
0
type MYREC is record n: Integer; str: System.Address; end record with Convention => C; 

This record contains data in an "unpinned" format, i.e. you don't use it directly. Instead, whenever you need to work with data, you must pin them:

declare Pinned_MYREC : String (1 .. MYREC_Value.n) with Import, Address => MYREC_Value.str; begin -- work with Pinned_MYREC end; 

This task can be automated using closures (or generics).

procedure Query_MYREC (MYREC_Value : MYREC; Process : not null access procedure (Pinned_MYREC : String)) is Pinned_MYREC : String (1 .. MYREC_Value.n) with Import, Address => MYREC_Value.str; begin Process.all (Pinned_MYREC); end Query_MYREC; 

And, often enough (when pragma Pack is not applied to access type), you can construct fat pointer type in a system-dependent way. Not a rocket science.

In my experience I had problems with fat pointer being not fat enough. In GNAT its second pointer points to bounds, and so they have to be allocated somewhere. So these custom crafted fat pointers can only reside within some container that provides a storage for bounds. And maybe patches fat pointer's second half to new bounds location when Adjust on new location happens.

In this project I provide type-safe string views and editors for direct Unbounded_String access; works on GNAT for Linux x86-64. In my project container is limited, so no Adjust needed. In my project fat pointers are access discriminants, so they can't leak.

2 Comments

From a language perspective, this can be dangerous. Constrained Ada String types are not required to be the same size as an array of C characters (because Ada Character types can be larger than 8 bits). If you are gonna overlay, overlay with the Interfaces.c.char_array type which is C compatible and then use the operations in Interfaces.C to convert back and forth to an Ada string.
Yeah, right, those possible differences from C are killing performance and joy of Ada programming. Storage unit may be not 8-bit. So much stuff is allowed to go wild by ISO. At the same time all the architectures in use are hard to imagine not fitting these assumptions. If they don't fit these assumptions, chances are there will be no Ada compiler at all than the exotic one existing and breaking common assumptions. Just in case it makes sense to write several pragma Assert, that's all about it IMHO.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.