Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting Ada String to C Void*

What is a good way to "cast" an Ada String to a System.Adress which would be equivalent to casting char* to void* in C.

I am interfacing which a C library. A C type has a property which is of type void*, and users of the library typically assign the address pointed to by a C-string as this value. E.g:

struct my_type {
    void* value;
};

int main() {
    my_type t;
    t.value = "banana";
}

How can I achieve the equivalent in Ada, starting with an Ada String?

I'm using this technique at the moment, but it seems fishy to me.

declare
    str : constant String := "banana";
    data : constant char_array := To_C(str);
    mine : my_type;
begin
    mine.value := data(data'First)'Address;
end;

I'm OK with any solution, even Ada 2012.

like image 355
Anthony Avatar asked Feb 13 '12 02:02

Anthony


1 Answers

You mention in a comment that you're using void* “because it should be able to take the address of anything; not just a string.”

So then, one has to ask how does the general-pointer translate into Ada, particularly in such a way as to take advantage of the typing and sub-typing features. I would submit that an “anything”, in this context, cannot be solved generally; that is to say, if you want to maintain the 'flexibility' of the construct you must sacrifice the advantages Ada provides with its type-system. Furthermore, I submit that presented as-is, it is generally impossible to reliably use for “anything”.

I say this because there is no method of determining even the length of the contained “anything.” If it's a string then the length is from the pointed-address, counting consecutively, until the first NUL character (ASCII 0). However, there is no method for determining the length if it is not a string (how would we know the length/size of array [1,2,3] or OBJECT)... and so we have no method for determining even the length of the “anything.”

Determining the length is an important factor in writing stable/secure code, because if you don't you're inviting buffer overflows.


But, leaving that off, if you can provide some information on the data, whether via parameter or changing my_struct, then we can use that information to build a better type-conversion. (In general, the more information you have about a type the better, because you can then check for validity-of-data in ways you couldn't before; or better yet, have the compiler check that for you.)

Type Data_Type is Array( Positive Range <> ) of Interfaces.Unsigned_8;
  For Data_Type'Component_Size Use 8;


Function Some_Data( Stream : not null access Ada.Streams.Root_Stream_Type'Class; 
                    Length : In Positive ) Return Data_Type is
  begin
     Return Result : Data_Type(1..Length) do
        For Index in Result'Range loop
           Interfaces.Unsigned_8'Read(Stream, Result(Index));
        end Loop;
     End Return;
  end Some_Data;

You can use the above to generate an array of 8-bit unsigned integers which would contain the data from the the stream. It outlines what you'd have to do in the general case, though since you're working with C-imports what you can do is modify it a bit so that a) there is a Temp variable which is an array like Result but use For Temp'Address Use [...] to overlay it on the my_type.value and then use the for-loop to copy it out.

like image 184
Shark8 Avatar answered Oct 23 '22 09:10

Shark8