I just wanted to check, why doesn’t the following type in Interfaces.C.Strings not work for you:
type chars_ptr_array is array (size_t range <>) of aliased chars_ptr;
Side note, for code blocks, use the “back tick” key rather than the apostrophe. It can usually be found next to the “1” key on your keyboard
If not you can try:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C.Strings;
use Interfaces.C.Strings;
use Interfaces.C;
procedure jdoodle is
type Char_Star_Star is array(size_t range <>) of aliased char_array_access
with Pack, Convention => C;
begin
null;
end jdoodle;
It cannot be unconstrained array, C does not have arrays. The equivalent of char** is access char_ptr.
However, there is a ready-to-use generic package Interfaces.C.Pointers to handle exactly this case:
package Pointers is
new Interfaces.C.Pointers (size_t, chars_ptr, chars_ptr_array, null_ptr);
function Foo return Pointers.Pointer;
pragma Import (C, Foo);
Result : constant chars_ptr_array := Pointers.Value (Foo);
Here Foo is a C function returning char**. The function Value returns an unconstrained array assuming termination by null_ptr. Note, that this is copying. If you want to avoid copying:
In_Situ : constant Pointers.Pointer := Foo;
You can walk the result using pointer arithmetic. Or you can map a constrained array on it:
In_Situ : constant Pointers.Pointer := Foo;
type Result_Array is
array (1..Integer (Pointers.Virtual_Length (In_Situ))) of
aliased chars_ptr;
pragma Convention (C, Result_Array);
Actual : Result_Array;
pragma Import (Ada, Actual);
for Actual'Address use In_Situ.all'Address;
Actual is an array of chars_ptr with the actual number of elements. I changed index type and range for convenience.
C does not have arrays, Even less does it have Ada arrays with (range <>) in the declaration. Char_Star_Star is an Ada array which cannot be returned or passed to a C subprogram. A C-compatible array must have static bounds.
The RM says it is ok as far as I can tell. In particular in section B.1:
14/3 Convention L has been specified for T, and T is eligible for convention L; that is:
15 * T is an array type with either an unconstrained or statically-constrained first subtype, and its component type is L-compatible,
Not to mention that in B.3 there is an unconstrained array type declaration that is considered C compatible:
23/3 type char_array is array (size_t range <>) of aliased char
with Pack;
My guess is that the compiler strips off stuff that Ada would normally require when the type is part of convention C, but either way unconstrained arrays are indeed allowed for interfacing with C as far as the RM seems concerned.
It may not have one defined in a way that you agree with, but the C standard does indeed define array types as part of C. It’s probably better to say that you believe C doesn’t have properly defined arrays so we don’t confuse folks.
In particluar, section 6.2.5 paragraph 20 of the C standards has:
An array type describes a contiguously allocated nonempty set of objects with a
particular member object type, called the element type.36) Array types are
characterized by their element type and by the number of elements in the array. An
array type is said to be derived from its element type, and if its element type is T, the
array type is sometimes called ‘‘array of T’’. The construction of an array type from
an element type is called ‘‘array type derivation’’.
Again, there’s always room to disagree with how they define it, but that’s not the same as it being absent from the language.
The compiler can skip the array bounds when passing it down to C, so char_array might be OK where char* is expected as an argument except the cases when C manages the pointer (e.g. calls free on it) or when C decrements the pointer .But the compiler cannot invent the bounds when a pointer like char* is returned.
It’s definitely unchecked programming at that point (as is all FFI), but it is still allowed and seems to work in GNAT. It just seems to shift the burden of managing the array size on the programmer. I don’t think the compiler invents bounds for pointers to arrays with convention C specified (I don’t know for sure, just a guess). It’s definitely implemented to work in GNAT at least. See the example:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C; use Interfaces.C;
with Interfaces.C.Strings; use Interfaces.C.Strings;
procedure jdoodle is
function strcat(Destination : char_array_access; Source : char_array_access) return char_array_access
with Import, Convention => C, External_Name => "strcat";
D : aliased char_array := "Hello " & (1..94 => char'Val(0));
S : aliased char_array := To_C("World");
R : char_array_access := strcat(D'Unchecked_Access, S'Unchecked_Access);
current : char;
index : size_t := 0;
begin
loop
current := R(Index);
exit when current = char'Val(0);
Put(Character(Current));
Index := Index + 1;
end loop;
end jdoodle;
You are cheating, strcat returns Destination which has bounds in front of it. Here is an example that returns C string with no bounds:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C.Strings; use Interfaces.C;
use Interfaces.C.Strings;
procedure Test is
function strerror (errnum : int) return char_array_access
with Import, Convention => C, External_Name => "strerror";
E : char_array_access := strerror (11);
function strerror1 (errnum : int) return chars_ptr
with Import, Convention => C, External_Name => "strerror";
E1 : chars_ptr := strerror1 (11);
begin
Put_Line ("Length=" & Integer'Image (E.all'Length));
Put_Line ("Length=" & size_t'Image (strlen (E1)) & " Value=" & Value (E1));
end Test;
The first version is a bug, the behaviour is undefined. The second version is correct.
I was more trying to show that it “can” work in some situations. Using absolutes is a very slippery slope. You were saying that an Ada array cannot be returned or passed to a C subprogram. I was showing you it can be. There are limitations on it for sure, but the language does allow it if used correctly (that’s a big if of course and it places the burden on the programmer to verify it is correct).
That’s also while I ended that with:
And then I agreed with you on using the Generic you suggested.
You are right, maybe I was too sloppy.
I wanted to stress that using arrays with C is a minefield. If you know what is going on it is OK. E.g. I saw AdaCore code converting String to C using Unchecked_Conversion. They simply know.
But in general case it is better to avoid arrays to be safe and remain as close to C as possible.
Thank you for the answers, it seems that generic pointers is the best solution for me. I have never thought about the solutions here and this tought me a lot.
type ZString is new String;
type ZWideString is new Wide_String;
function zs2s(strz :zstring) return string;
function s2zs(s : string) return zstring;
function ws2zws(s : wide_string) return ZWideString;
function zws2ws(strz :ZWideString)return wide_string;
function s2zs(s : string) return zstring is
begin
return ZString(s & character’val(0));
end s2zs;
function ws2zws(s : wide_string) return ZWideString is
begin
return ZWideString(s & wide_character’val(0));
end ws2zws;
function zws2ws(strz :ZWideString)return wide_string is
l : integer;
begin
l := 0;
for i in 1 … strz’length loop
if strz(i) = wide_character’val(0) then exit; end if;
l := l + 1;
end loop;
if l > 0 then return wide_string(strz(strz’First … l)); else return “”; end if;
end zws2ws;
const char* is zstring
function zs2s(strz :zstring)return string is
l : integer;
begin
if strz’length = 0 then return “”; end if;
l := strz’last - 1;
return string(strz(1 … l));
end zs2s;