Hi Attila,
Re: https://bugs.launchpad.net/cffi/+bug/1528719
(There is an error in the sample code, the struct is called 'struct-pair, not 'pair.) Here is a simpler exhibition of the problem
(sumpair (convert-to-foreign '(40 . 2) '(:struct struct-pair))) The value #.(SB-SYS:INT-SAP #X7FFFEC003560) is not of type LIST. [Condition of type TYPE-ERROR]
So, you've said the function is call-by-value, and then you want to pass a pointer to the structure, and it makes sense there's an error. You have to assume a translator, because that's the only way to have a structure by value.
I understand the desire to be able to pass a pointer instead, but my approach would be different. I'd change the function argument definition and extend the syntax to say that you're going to pass a pointer, e. g.
(defcfun "sumpair" :int (p (:struct struct-pair) :pointer))
which I would have expand to something like
(DEFUN SUMPAIR (P) (WITH-FOREIGN-OBJECTS ((CFFI::ARGVALUES :POINTER 1) (CFFI::RESULT ':INT)) (MEM-REF (PROGN (LOOP :FOR CFFI::ARG :IN (LIST P) :FOR COUNT :FROM 0 :DO (SETF (MEM-AREF CFFI::ARGVALUES :POINTER COUNT) CFFI::ARG)) (CFFI::CALL (CFFI::PREPARE-FUNCTION "sumpair" ':INT '((:STRUCT STRUCT-PAIR)) ':DEFAULT-ABI) (FOREIGN-SYMBOL-POINTER "sumpair") CFFI::RESULT CFFI::ARGVALUES) CFFI::RESULT) :INT)))
So now you can do (sumpair (convert-to-foreign '(40 . 2) '(:struct struct-pair))) 42
I don't like the idea of hacking up translate-into-foreign-memory because you're doing extra work that way, and as you can see, the expansion can be simpler than the translated case. The only disadvantage of this approach is that you'll have to have two different functions if you will be calling the same function with a CL object and with a foreign pointer. I imagine that this won't happen very often; I'm guessing this is for chaining together foreign calls in CL, where the foreign structure never sees the light of day (=lisp form).
(The defcfun won't expand the way I've shown yet, but the pseudo-expansion actually can be compiled and run.)
What do you think?
Liam
hi Liam,
Re: https://bugs.launchpad.net/cffi/+bug/1528719
(There is an error in the sample code, the struct is called 'struct-pair, not 'pair.)
thanks, fixed.
Here is a simpler exhibition of the problem
(sumpair (convert-to-foreign '(40 . 2) '(:struct struct-pair))) The value #.(SB-SYS:INT-SAP #X7FFFEC003560) is not of type LIST. [Condition of type TYPE-ERROR]
So, you've said the function is call-by-value, and then you want to pass a pointer to the structure, and it makes sense there's an error.
well, that depends on the perspective you take. as a user i certainly didn't use a '(:pointer (:struct struct-pair)) in my original WITH-FOREIGN-OBJECTS example, so i'm telling CFFI to deal with structs, not pointers.
but from the perspective of the implementation it will of course always be a pointer, because that's how the lisp side can deal with foreign objects.
your example above is somewhat trickier, but there's nothing that stops CFFI from passing around foreign structs as pointers internally (which is an implementation detail), and deal with them either as pointers or as values based on the ffi type info.
You have to assume a translator, because that's the only way to have a structure by value.
i don't see why you say that (or maybe what you mean by 'translator').
we can expand into code that dispatches on the value type at runtime (minus the ,@body duplication and the wrong dispatch type):
(defmethod expand-to-foreign-dyn-indirect :around (value var body (type translatable-foreign-type)) (let ((*runtime-translator-form* `(if (typep ,value 'foreign-pointer) (let ((,var ,value)) ,@body) (with-foreign-object (,var ',(unparse-type type)) (translate-into-foreign-memory ,value ,type ,var) ,@body)))) (call-next-method)))
i don't see how to add this cleanly to the current codebase, but with this every relevant test succeeds.
(defcfun "sumpair" :int (p (:struct struct-pair) :pointer))
i wouldn't do this if it's avoidable. i as a user never mentioned pointers, i want to be dealing with structs. and for all intents and purposes they should behave as structs when accessing them through the CFFI API. it's an implementation detail that on the lisp side, internally to the CFFI implementation, they travel around as pointers allocated into the C heap. it can (and IMO should) be completely transparent to the user. unless i'm missing something.
The only disadvantage of this approach is that you'll have to have two different functions if you will be calling the same function with a CL object and with a foreign pointer.
i think i'd go the opposite way: make runtime translation optional and by default only do what i described above. for simple structs and/or hand customized FFI's the runtime translation may come handy sometimes, but for generated FFI's they are just bringing in complexity, so i think it shouldn't be the default.
I imagine that this won't happen very often; I'm guessing this is for chaining together foreign calls in CL, where the foreign structure never sees the light of day (=lisp form).
well, in my case it's the bluez API that in 99% of the cases uses pointers for the bdaddr struct, except one single case (which is probably only a thinko). i get all my bdaddr's from the bluez api itself, and many of them i store for long term.
the actual example i'm stuck with:
(bind ((peer-address "00:55:44:33:22:11")) (with-open-hci-socket (socket :remote-device peer-address) (cffi:with-foreign-objects ((peer-bdaddr 'bluez:bdaddr_t) (handle 'bluez:uint16_t)) (bluez:string->bdaddr peer-address peer-bdaddr) (c-fun/rc bluez.ffi::hci_le_create_conn socket 4 4 0 bluez:+le_public_address+ peer-bdaddr bluez:+le_public_address+ #xf #xf 0 1000 0 0 handle 25000))))
and while we are at it, the BLUEZ:STRING->BDADDR defcfun expects a pointer, so i would even go as far as to suggest that it's a bug that CFFI doesn't signal an error here. i'd expect the lisp side to also have an address-of operator and demand properly matching types.
this ties back to my recent proposal about rethinking the CFFI API. type information should be tracked/derived at compile time and apropriate errors signaled.