问题
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.
回答1:
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.
回答2:
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.
回答3:
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.
回答4:
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.
来源:https://stackoverflow.com/questions/23364380/passing-an-array-allocated-from-a-c-routine-to-ada