Subject: [clisp-list] FFI:C-POINTER and C null pointers

From: "Hoehle, Joerg-Cyril" <[email-masked]>
Date: Wed Jul 17 01:49:01 2002

Hi,

Context:
>> * Honorable Aurelio Bignoli <[email-masked]>
wishes to pass either a FFI:C-POINTER interface type (FOREIGN-ADDRESS Lisp object type) or a NULL pointer to a foreign function - a somewhat common request.

Sam asks:
>does
>        (h (c-ptr-null REALLY-HAIRY-STRUCT) :in)
>help?
Not directly. This is a strictly incompatible type (i.e. ptr** instead of ptr*). The C interface code would have to be changed to accomodate this.

This sort of double indirection is what CLISP's regexp module uses. regexp does this essentially in order to receive two results from the initialization function: a status code, and the pointer - if successful. So it is used with mode :out. In C, out parameters have to be used to mimic multiple returns (unless you return a single struct with two slots).

>I defined an external C variable in hairy.c:
>Is there any better solution? Or is such a null foreign pointer
>already defined somewhere? 

Newer CLISP (CVS only) may include exactly such a thing, named ffi_user_pointer (or so). I believe this patch of mine was accepted into CVS. Try
(typep (setq v0 (ffi::lookup-foreign-variable "ffi_user_pointer"
	  (ffi::parse-c-type 'ffi:c-pointer)))
  'ffi:foreign-variable)
(EVAL-WHEN (LOAD) (DEF-C-VAR zp ... "ffi_user_pointer" ...)
and use CAST, SETF etc. on zp.

>AFAIK there isn't any way to create a foreign
>object at runtime.
These longstanding shortcomings have been addressed.
Newer CLISP let you play with run-time foreign variables, run-time functions and more.

Other solutions or work-arounds:
 o devise a second ffi-call-out that accepts null (and only null), i.e. (foo null :in) -- used I believe in the linuxbindings.
 o revisit C interface not to accept NULL
 o I forgot

Conceptually, I believe one must realize that passing NULL when a FILE* or HAIRY_PTR* is expected is a braindamage from C, which endangers reliable software. From a functional point of view, there is no such thing as passing an object of the wrong type (NULL) to a function declared as accepting type HAIRY. This is hidden polymorphism. I like the functional approach to interface descriptions and functionality.
Java inherited the same braindamage from C. Try supplying a the Java #null object to many functions accepting an object: they crash at some stage with nullpointerexception, instead of simply refusing to be passed in that #null object.
And then, where does Java document whether receiving a #null is acceptable for each parameter for each method?

I realize this is not really pragmatic approach, it sounds rather puristic. When dealing with foreign worlds, perhaps a pragmatic approach would fit CLISP better ? But then, what do we have type definitions and type checks for, even in C?

CVS CLISP may also contain a few utility functions of mine, namely UNSIGNED-INT<->FOREIGN-ADDRESS conversion functions (FOREIGN-ADDRESS-UNSIGNED and its converse).

For your purpose, (UNSIGNED-FOREIGN-ADDRESS 0) may be safer to use, since the ffi_user_pointer could be set to any arbitrary value by other application code.
1. Break [20]> (ffi::unsigned-foreign-address 0)
#<FOREIGN-ADDRESS #x00000000>
(defconstant FFI-NULL (ffi:UNSIGNED-FOREIGN-ADDRESS 0)) ; doesn't survive/resurrect from image files.

Part of the pragmatic approach might be: why not make CLISP accept nil when a C-POINTER type is expected? The answer is that there are some dangerous issues here, right at the next turn (and a cascade of other questions, e.g. what value to return for C-POINTER: nil or #<FOREIGN-ADDRESS 00000000>? FFI so far obviously returns a FOREIGN-ADDRESS, my AFFI returns NIL, so the result is not truly opaque in AFFI). It's been some monthes since I last digged into this, trying to provide a precise answer. People also have been begging for c-ptr-null :out or :in-out combinations, which seems appealing, but just doesn't fit the model of types.
When one wishes to supply a C function with either NULL or a pointer, the answer (so far) is to forget about CLISP's high-level FFI and :in/:out parameters in general, to put oneself at a C level, where such things don't exist, and feed functions with pointers or NULL (as is possible in CMUCL: it incorporates two levels of abstraction in its FFI).

Regards,
	Jorg Hohle.

---

From: Aurelio Bignoli <[email-masked]>
Date: Thu Jul 18 13:32:02 2002

>>>>> "Jorg" == Hoehle, Joerg-Cyril <[email-masked]> writes:

    Jorg> (ffi::lookup-foreign-variable "ffi_user_pointer"
    Jorg> (ffi::parse-c-type 'ffi:c-pointer))) 'ffi:foreign-variable)

really interesting! I didn't know of ffi::lookup-foreign-variable.

    Jorg> Other solutions or work-arounds: 
    Jorg>  o devise a second ffi-call-out that accepts null (and only
    Jorg> null),

I examined this solution, but I discarded it because I was going to
write a further Lisp layer with functions like this:

(defun high-level-do-something (&key (hairy-struct NULL))
  (do-something hairy-struct))

Using a NULL foreign variable as default value for :hairy-struct would
save an (if (null hairy-struct) ...) in function's body.

    Jorg> Conceptually, I believe one must realize that passing NULL
    Jorg> when a FILE* or HAIRY_PTR* is expected is a braindamage from
    Jorg> C, which endangers reliable software.

I agree with you, but I didn't write the library I'm interfacing, and
modifying the (free) sources is not a viable solution.

    Jorg> For your purpose, (UNSIGNED-FOREIGN-ADDRESS 0) may be safer
    Jorg> to use, since the ffi_user_pointer could be set to any
    Jorg> arbitrary value by other application code.

I think I'll try this solution, I only need to perform a cvs update on
my local copy of CLISP sources.

Many thanks for your hints and useful insights into CLISP ffi.

Aurelio