[racket] FFI question again - how to get a string back from C

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Sat Jun 11 06:16:49 EDT 2011

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))))


Posted on the users mailing list.