Subject: [clisp-list] Re: FFI question (FFI cookbook)

From: "Hoehle, Joerg-Cyril" <[email-masked]>
Date: Tue Feb 12 03:16:03 2002

myriam wrote:
  > > commented out in CLISP/modules/bindings/linuxlibc6/linux.lisp:
  > > (def-c-call-out gethostname (:arguments (name c-pointer) (len size_t)) ; ??
  > >                             (:return-type int))
  > > (def-c-call-out sethostname (:arguments (name (c-pointer) (len sizet)) ; ??
  > >                             (:return-type int))

Spoiler ahead! The FFI cookbook, chapter N (not 1)

Reini Urban responds:
>(ffi:def-c-call-out gethostname (:arguments (name c-string) (len size_t))(:return-type int))
>(ffi:def-c-var c-hostname (:type c-string) (:alloc :malloc-free))
>(if (zerop (linux:gethostname c-hostname 15)    ; 0 on success
>    (defvar hostname (ffi:deref c-hostname)))   ; deref converts c-string to lisp type
[unbalanced parentheses?]
>and link the glibc to clisp. don't know if the dynamic-ffi works here.

CLISP's FFI can do much more for you. There is no external c-hostname
variable. You only need a small wrapper written in Lisp and normally
nothing in C. This text attempts to explain the story. Sadly, my code
is untested, since I didn't install CLISP on a UNIX machine. However,
the concepts are there, hopefully understandable.

C declarations are completely insufficient to know how the arguments
are actually to be used. I recommend reading "H/Direct: A Binary
Foreign Language Interface for Haskell" to appreciate the needed
expressiveness of any FFI, including CLISP's -- that's why there is
c-ptr, c-pointer, c-ptr-null, c-array-ptr, c-array, c-array-max etc.

See section Foreign-language integration of
http://research.microsoft.com/Users/simonpj/Papers/papers.html
H/Direct: A Binary Foreign Language Interface for Haskell

Topics covered below: allocation, some array types, character encodings

In the present example, you can use allocation :alloca, like you'd do
in C: stack-allocate a temporary. Why make things worse when using
Lisp than when using C?

gethostname(name,len) follows a typical pattern of C "out"-parameter
convention. It expects a pointer to a buffer it's going to fill. So
you must view this parameter as either :out or :in-out.

Additionaly, one must tell the function the size of the buffer. Here
<len> is just an :in parameter. Sometimes this will be an :in-out
parameter, returning the number of bytes actually filled in.

So <name> is actually a pointer to an array of up to <len> characters
(or bytes? -- see below), regardless of what the poor "char *" C
prototype says, to be used like a C string (0-termination).

How many elements are in the array? Luckily, in our case, you can find
it out without calculating the sizeof() a C structure. It's a hostname
that will be returned. The Solaris 2.x manpage says "Host names are
limited to MAXHOSTNAMELEN characters, currently 256."

This yields the following useful signature for your foreign function:

(ffi:def-c-call-out gethostname
  (:arguments (name (ffi:c-ptr (ffi:c-array-max ffi:char 256))
		    :out :alloca)
	      (len ffi:int))
  (:return-type ffi:int))

A (:return-type BOOLEAN) could have been used here (Solaris says it's
either 0 or -1).

(defun myhostname ()
  (multiple-value-bind (success name)
      ;; :out or :in-out parameters are returned via multiple values
      (gethostname 256)
    (if (zerop success)
	(subseq name 0 (position #\0 name))
	(error ... ; errno may be set
          ...))))
(defvar hostname (myhostname))

Possibly SUBSEQ and POSITION are superfluous, thanks to C-ARRAY-MAX as
opposed to C-ARRAY:
(defun myhostname ()
  (multiple-value-bind (success name)
      ;; :out or :in-out parameters are returned via multiple values
      (gethostname 256)
    (if (zerop success) name
	(error ... ; errno may be set
          ...))))

Unicode and possible character encoding problems w/ #+UNICODE

I have no experience with character conversions in CLISP. Maybe the
first solution above is problematic because, depending on
ffi:*FOREIGN-ENCODING*, the resulting string is not a valid one in the
encoding (it's shorter, since it's zero/NUL-terminated and undefined
behind the \0).

OTOH the above ffi:C-ARRAY-MAX may already take care of this (contrary
to just ffi:c-array). The above call to POSITION maybe
superfluous. The source code in FOREIGN.D seems to indicate that
conversion is safe (not converting more than <len> bytes). I didn't
look if some call to SUBSEQ and POSITION is still needed because we
don't want the full array that CLISP may have allocated prior to the
call. Anyway, I don't like to look at source code for this, it should
be clear from the documentation.

So another approach would be to use 8-bit bytes instead (if your OS
uses an 8 bit char, and not UNICODE internally), together with
ext:CONVERT-STRING-FROM-BYTES, doing the conversion ourselves. We need
another prototype:

(ffi:def-c-call-out gethostname
  (:arguments (name (ffi:c-ptr (ffi:c-array ffi:uint8 256))
	; no more c-array-max, we'll do ourselves
		    :out :alloca)
	      (len ffi:int))
  (:return-type ffi:int))

(defun myhostname ()
  (multiple-value-bind (success name)
      (gethostname 256)
    (if (zerop success)
	(ext:convert-string-from-bytes
	  name ffi:*foreign-encoding* :end (position 0 name))
	(error ... ; errno may be set
          ...))))

Possible bugs in this explanation: :IN-OUT may be needed instead of
just :out (:out may be unusable), use of POSITION unclear, may need
257 instead of 256 element or call <len> with 255, ...

I hope this helps,
	Jörg Höhle.

---

From: myriam <[email-masked]>
Date: Tue Feb 12 07:32:04 2002

Beautiful! It works!!

Tested on Solaris 8

>       ;; :out or :in-out parameters are returned via multiple values

I didn't know that, thanks. 

                                    myriam

---

From: Sam Steingold <[email-masked]>
Date: Tue Feb 12 17:38:02 2002

> * In message <DFD875E85664D3118FA6080006277DE704F04FED@U8PN2>
> * On the subject of "[clisp-list] Re: FFI question (FFI cookbook)"
> * Sent on Tue, 12 Feb 2002 12:15:32 +0100
> * Honorable "Hoehle, Joerg-Cyril" <[email-masked]> writes:
>

cool - give us more!  give us the rest of the cookbook!
based on your suggestions, I enabled gethostname() in linux.lisp and
added some wrappers (see CVS).  thanks!

maybe you could also tell us why errno does not work?

*** - FFI:DEREF is only allowed after FFI::FOREIGN-VALUE: (FFI:DEREF (LINUX:__errno_location))

LINUX:__errno_location appears to be a function returning the errno.

-- 
Sam Steingold (http://www.podval.org/~sds) running RedHat7.2 GNU/Linux
Keep Jerusalem united! <http://www.onejerusalem.org/Petition.asp>
Read, think and remember! <http://www.iris.org.il> <http://www.memri.org/>
I don't like cats! -- Come on, you just don't know how to cook them!

---

From: Kaz Kylheku <[email-masked]>
Date: Tue Feb 12 19:59:02 2002

> LINUX:__errno_location appears to be a function returning the errno.

Indeed, this is one of the things I worked around in Meta-CVS. I used
(linux::__errno_location) to get to errno values.