Common Lisp CFFI: pointer to the pointer
Asked Answered
I

1

6

I am trying to write the CFFI wrapper for Sundials CVODE library. SWIG was choking on Sundials headers since they are quite interconnected and SWIG couldn't find the right headers, so I did it by hand: a bit laborious but I've managed.

Now I'm trying to test if it works correctly. For now, just simply creating the "problem object" and deleting it. That is where the problem starts. So, the "problem object" is allocated via function

SUNDIALS_EXPORT void *CVodeCreate(int lmm, int iter);

For which I created the wrapper:

(cffi:defcfun "CVodeCreate" :pointer
  (lmm :int)
  (iter :int))

PS. SUNDIALS_EXPORT (at least on Unix's) is basically nothing.

Now, to destroy the object, Sundials uses its own function:

SUNDIALS_EXPORT void CVodeFree(void **cvode_mem);

So, I need to pass it the reference to the object created by CVodeCreate. In C, if my memory is not at fault, I would have done something like CVodeFree(&problem_object). In CL I've written this wrapper for the function:

(cffi:defcfun "CVodeFree" :void
  (cvode-mem :pointer))

So, here COVDE-MEM is a pointer to a pointer. Question is how to get the pointer of the pointer in CL/CFFI? Here is the beginning of the code:

(defvar *p* (cvodecreate 1 2))

(PS. Don't worry about the numbers passed to CVODECREATE, they just tell which methods to use, still need to defined constants to make it more readable)

So *P* is something like

#.(SB-SYS:INT-SAP #X7FFFE0007060)

If I pass it directly to CVODEFREE, it ends up in error:

CL-USER> (cvodefree *p*)
; Evaluation aborted on #<SIMPLE-ERROR "bus error at #X~X" {1005EC9BD3}>.

I've tried passing (CFFI:POINTER-ADDRESS *P*) but it results in similar "bus error..." (not even sure if this function returns what I need). I've also tried to do (CFFI:MAKE-POINTER (CFFI:POINTER-ADDRESS *P*)), once again without any success.

This question suggests this approach:

(cffi:with-foreign-object (p :pointer)
           (setf (cffi:mem-ref p :pointer) (cvodecreate 1 2))
           (cvodefree p))

This works (at least it doesn't throw an error). I think I understand how it works: it creates (allocates the memory for) a pointer-to-a-pointer P, whose MEM-REF (or in C terms would be dereferencing *p) is filled by the result on CVODECREATE. Finally, I'm passing this pointer-to-a-pointer to CVODEFREE, which expects exactly this. Finally, the memory allocated for P is freed once the form finished. Is this the correct approach? And is it the only one I can take?

Incorrect answered 7/3, 2016 at 10:48 Comment(2)
Would be very interesting to know why they chose that signature for CVodeFree. Seems like "over-engineering" and trying to be extra smart.Silverts
@DanielJour As far as I remember this way is not unusual in C-world. Perhaps, they free the memory and NULL the pointer there. But I agree, totally unnecessary.Incorrect
M
5

Yup, your approach looks right, here is a small test to show the concept that can be run straight from the repl.

(let* (;; a float
       (v0 32s0)

       ;; a pointer to a float foreign memory
       (p0 (cffi:foreign-alloc :float :initial-element v0))) 

  ;; a new pointer
  (cffi:with-foreign-object (p1 :pointer)

    ;; make the new pointer point to the first pointer
    (setf (cffi:mem-aref p1 :pointer) p0)

    ;; dereferencing twice should give you the original number
    (cffi:mem-aref (cffi:mem-aref p1 :pointer) :float)))

p.s. I'm sure you knew this by now, sorry it took so long to get you an answer. Hopefully this can help others

Marcoux answered 26/7, 2016 at 15:7 Comment(1)
mem-aref helped indeed. Thanks!Heterogenous

© 2022 - 2024 — McMap. All rights reserved.