Subject: [clisp-list] linux:ioctl

From: "Hoehle, Joerg-Cyril" <[email-masked]>
Date: Wed Mar 6 02:05:04 2002

Hi,
[adding another section to my FFI cookbook work in progress :)]

There's an inherent conflict between type-safety and "anything goes" functions like ioctl (or printf). FFIs generally require some parameter type information. Therefore they don't fit such functions well.

The third argument to ioctl(), arg, can be one of:
 o an integer (or bit mask),
 o a string (or a pointer to a memory location to be read as a string),
 o a pointer to an integer or bit mask, when receiving values (:out usage pattern),
 o a pointer to some structure containing further information,
 o ... I didn't scan the manpages for ioctl, termio, streamio any further.
The actual usage of arg is defined by the second parameter.

This scenario is similar to printf(format, args...) et al where the exact type of the rest args is to be deduced from the first argument, the format string. Modern compilers do some safety checks (type consistency), but they can only do so when the format string is known at compile time (e.g. it is a literal "%d%c%s" instead of taken from a variable).

The usual solution is as you pointed out: split the usage patterns. Having separate setters and readers is typical for Lisp (and Smalltalk, Java, xyz). Some may argue it's easier to read or to assess code correctness. Furthermore, having several FFI definitions depending on various types of arguments is almost a requirement, as I explain below.

Another solution is to internally dispatch depending on the types of arguments. This can only be done where types actually differ. In AllegroCL, foreign pointers being integers (AFAIK), pointers are indistinguishable from integers, so this technique would not be applicable in general there.

What I mean is have several definitions around, using one of the following depending on the actual arguments:
 (:arguments ... (arg ffi:int))
 (:arguments ... (arg ffi:c-string))
 (:arguments ... (arg (ffi:c-ptr (ffi:c-struct ...)))) or a generic
 (:arguments ... (arg (ffi:c-pointer))) and hope for CAST (not working in CLISP)
 (:arguments ... (arg ffi:int :out))
 ...

This technique also suffers from lack of generality. While you could dispatch depending on an integer or string received as argument, you cannot do so when you expect an integer or string to be returned via an :out parameter. You need some way to express and tell the implementation what to expect.

A join of the two methods would be an attempt to minimize the number of entry functions and provide extra arguments indicating the types of other arguments or return values (similar to MAP or COERCE).

> Could ioctl be defined as one function with :in-out parameters,
> instead of being split up into two?

First, as seen above, there are even more than two forms. In your examples, you only covered the ffi:int :in and :out cases. Other ioctl() work on strings, buffers, structures etc. You defined ioctl-read-int and ioctl-set-int.

:in-out would not work because it passes a pointer to an int, whereas :in passes the integer value.

What you can do is define a single API entry taking ideas from MAP or COERCE, i.e. provide a description of the expected types and then dispatch internally. This could work for ffi:int, ffi:c-string in both :in and :out forms. It's more work when arg is a pointer to some structure.

Even c-string :out may not work because it's unclear where the memory to contain the string's characters comes from. It all higly depends on the actual ioctl() call.  For example, Solaris' manpage on streamio says about the command I_LOOK: "The buffer pointed to by arg should be at least FMNAMESZ+1 bytes long." [...strings vs buffers covered somewhere else in my upcoming article].

I advise not to try to mimic C and try and pass everything as either a ffi:int or ff:c-pointer, because you loose all of the comfort of what the various FFI give you: c-string conversions, function to pointer conversion, integers vs. pointers to one integer to hold a result value, etc. You'd then need to restore all of them by hand. Furthermore, some implementations, CLISP among them, won't give you pointers to the internals as others do (trivial with AllegroCL, available with CMUCL, TODO others).

> If a C function expects a (pointer to) a function in its argument
> list, can I give it a lisp function.  Ie, will the ffi convert it to
> the correct type for C, automatically.  What should the 
> allocation be?  

The conversion will occur in CLISP only if you declare the foreign function to take a function as argument. Using ffi:c-pointer will signal an error.
 (:arguments ... (arg (ffi:c-function (:arguments ...) (:return-type ...))))

Allocation for passing functions: Use the default (don't specify). Remember it's solely a pointer that gets passed around. For a Lisp function, CLISP will create a trampoline under the woods: that's what's pointed to. CLISP will remember the correspondance between the possibly new trampoline and the function indefinitely internally, since it has no means to know when the (pointer to the) trampoline will not be used anymore by foreign code.

I have no experience yet whether it's enough to just say
 (:arguments ... (arg (ffi:c-function))) or possibly
 (:arguments ... (arg (ffi:c-function (:return-type ffi:int))))
or whether a complete type must be specified and whether type coherence will be checked when passing an actual Lisp or foreign function object.
Please report.

I'd appreciate whether you find my elaboration useful, because I'd like to turn it (or part of it) into part of an article (and cookbook entry) I'm writing about FFI for CLISP, AllegroCL, CMUCL, CormanLisp and Lispworks.

BTW, we touch an area where I find the CLISP FFI quite limited. I once advocated foreign function definitions with local scope (as found e.g. in CMUCL with (alien-funcall (extern-alien ...))) since their pattern can be highly application dependent (as seen here). I dislike globally visible definitions of functions whose call pattern is so specialized that only a wrapper can call them. The wrapper is what needs to be global (and exported). I hope that my upcoming article will make more of the FFI design issues clear and lead to a more useful FFI for CLISP.

Hope this helps,
	Jorg Hohle.

---

From: Reini Urban <[email-masked]>
Date: Thu Mar 7 12:23:06 2002

> I'd appreciate whether you find my elaboration useful, because I'd like to turn it (or part of it) into part of an article (and cookbook entry) I'm writing about FFI for CLISP, AllegroCL, CMUCL, Co…

yes, very useful.

> I once advocated foreign function definitions with local scope (as found e.g. in CMUCL with (alien-funcall (extern-alien ...))) since their pattern can be highly application dependent (as seen here). I dislike globally visible definitions of functions whose call pattern is so specialized that only a wrapper can call them. The wrapper is what needs to be global (and exported). 

ack.
-- 
Reini Urban
http://xarch.tu-graz.ac.at/home/rurban/