[racket] FFI question again - how to get a string back from C
Hi Ryan,
thank you very much, this works very well!
I am not sure yet whether it'd be desirable to pass in the buffer to the function,- it's more work for the client, but in any case, the client will have to specify the buffer length.
Regarding garbage collection, it should not really make a difference, should it - the buffer will be automatically garbage-collected by racket (in one case, directly after the function call, earliest, in the other, when the client does not use it any more), is that correct?
Still regarding the conversion of the byte-string to a string, this is (besides garbage collection) yet another topic I still have to sort out and understand better - at the moment I'm not sure "who decides" the character set to be used, the OCILIB library, racket, or even the OS (the only thing I'm quite positive about, it will NOT be the database talked to by the c library... :-) ). But this is something I have to investigate.
Many thanks again for your help!
Sigrid
Am 11.06.2011 um 12:16 schrieb Ryan Culpepper:
> On 06/11/2011 12:54 AM, keydana at gmx.de wrote:
>> Hi again,
>>
>> sorry for again asking such a basic FFI question, but I have a problem getting an output string from the C side...
>>
>> E.g. in one case, in my first attempt
>>
>> (def-ocilib datetotext OCI_DateToText : (date_ptr : _pointer) (fmt : _string) (size : _int) (strval : (_ptr o _string)) -> (result : _bool) -> (values strval result))
>>
>> I simply tried using (strval : (_ptr o _string)) for the return string (the argument size indicates the desired size for the output string).
>>
>>
>> After the experience with the null-terminated strings from my recent post, I also tried an input-output-arg, passing a null-terminated string in to C:
>>
>> (def-ocilib datetotext OCI_DateToText : (date_ptr : _pointer) (fmt : _string) (size : _int) (strval : (_ptr io _string) = (gen-output-string 100))-> (result : _bool) -> (values strval result))
>>
>> where gen-output-string pads an empty string to a specified length.
>>
>> But in both cases, I get the same error:
>>
>> ptr-ref: expects type<cpointer> as 1st argument, given: #<bad-value>; other arguments were: #<ctype>
>>
>> Honestly, I have no idea what the problem might be (or how to debug/investigate it), and would very much appreciate any hints... :-)
>
> I'm not sure if it's possible to solve this problem with (_ptr o ???) or (_ptr io ???); I'd be very interested to hear if it's possible.
>
> It looks like the function wants you to pass in a buffer for it to write into. The tricky part seems to be figuring out where the data ends. I assume the end is indicated by a null terminator. (The APIs I've worked with have had the courtesy to put the length of the string in an output parameter, which makes it easier.) Here's how I would do it:
>
> Use _bytes to represent the buffer. A type like _string won't work, because the C function would be writing to a converted copy of the string; it wouldn't modify the string you gave it.
>
> The C function writes the formatted date, followed by a null terminator, to the buffer. Now you need to extract only the formatted date, the part before the null terminator. You could search for the position of the null terminator yourself, or you could use an internal Racket function that makes a byte string ("bytes") from a null-terminated byte array.
>
> (define-ffi-definer define-racket (ffi-lib #f))
>
> (define-racket scheme_make_byte_string
> (_fun _bytes -> _racket))
>
> For the main function, if you want to take the buffer as a parameter (because you calculated the size elsewhere or because you want to reuse the buffer), define the function this way:
>
> ;; OCI_DateToText : date-pointer string bytes -> (values boolean bytes)
> (define-ocilib OCI_DateToText
> (_fun (date fmt buffer) ::
> (date : _pointer)
> (fmt : _string)
> (size : _int = (bytes-length buffer)
> (buffer : _bytes)
> -> (result : _boolean)
> -> (values result
> (scheme_make_byte_string buffer))))
>
> I find it useful to specify the parameters explicitly; that's what the part before the '::' is. It's sometimes mandatory, especially when you're calculating some parameter values based on others; I just always write them out unless the signature is trivial. The part after the first '->' is a type-spec that specifies what the foreign function returns. The part after the second '->' is a normal Racket expression that determines what the Racket wrapper returns.
>
> Note that the definition above gives you the result as bytes; you can convert it to a string using bytes->string/???, whatever encoding it is. Or you could use Racket's scheme_make_utf8_string instead of scheme_make_byte_string if that's the right encoding.
>
> If you want to create the buffer locally:
>
> ;; OCI_DateToText : date-pointer string -> (values boolean bytes)
> (define-ocilib OCI_DateToText
> (_fun (date fmt) ::
> (date : _pointer)
> (fmt : _string)
> (size : _int = 100) ;; FIXME: big enough?
> (buffer : _bytes = (make-bytes size))
> -> (result : _boolean)
> -> (values result
> (scheme_make_byte_string buffer))))
>
> HTH,
> Ryan