Re: Help me understand FFI callouts in this context?

Ryan Culpepper <[email protected]>
Newsgroups gmane.comp.lang.racket.user,gmane.lisp.scheme.plt
Message-ID <[email protected]>
On 10/25/19 12:45 AM, Sage Gerard wrote:
> I am porting some C++ code to Racket that uses a function pointer.
> 
> C++ origin: See 294 through 306: 
> https://github.com/Erkaman/vulkan_minimal_compute/blob/master/src/main.cpp#L294 
> 
> Racket destination: 
> https://github.com/zyrolasting/racket-vulkan/blob/master/examples/mandelbrot.rkt#L240
> 
> How do I use function-ptr on debug-report-callback to produce a valid 
> function pointer matching this signature 
> <https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/PFN_vkDebugReportCallbackEXT.html>? 
> 
> 
> My current error comes from line 250, on the expression (function-ptr 
> debug-report-callback _PFN_vkDebugReportCallbackEXT):
> 
> ; function-ptr: contract violation
> ;   expected: (and ctype? (lambda (ct) (eq? 'fpointer (ctype->layout ct))))
> ;   given: #<ctype>
> 
> It seems that my error was that _PFN_vkDebugReportCallbackEXT was 
> (_cpointer/null (_fun ...)), but I am not sure what should take its place.
> 
>   * If I substitute _fpointer for _PFN_vkDebugReportCallbackEXT, I get
>     the "application; not a procedure" error.
>   * If I substitute the _fun form with the signature I want, then I get
>     "#<ctype>->C: argument is not `#<ctype>' pointer"
> 
> I suspect this has something to do with the /callout/ concept from the 
> manual, but I don't understand how it applies here.

The use of _cpointer/null seems wrong: its first argument is interpreted 
as a tag, so that's the source of the last error you mentioned. The 
`_cpointer/null` is unnecessary anyway, so just delete it:

   (define _PFN_vkDebugReportCallbackEXT (_fun ....))

On line 250: I don't think you need to use function-ptr. The field 
setter will automatically convert debug-report-callback (I assume that's 
a Racket procedure) into a *callback* function pointer.

A *callout* is a foreign function gets converted into a Racket 
procedure; a *callback* is when a Racket procedure gets turned into a 
function pointer so it can be called from foreign code. Both are 
described by _fun ctypes.

Here's a simpler example. Suppose you have the following C code:

   /* demo.c */
   /* gcc -fpic -shared -o demo.so demo.c */

   int an_int_fun(int x) {
     return x + 1;
   }

   typedef int (*int2int)(int);

   int apply_to_twelve(int2int f) {
     return (*f)(12);
   }

   struct two_funs {
     int2int f;
     int2int g;
   };

   int apply_two_funs_and_sum(struct two_funs *tf, int arg) {
     return (tf->f)(arg) + (tf->g)(arg);
   }

Here Racket bindings for the foreign code:

   (require ffi/unsafe ffi/unsafe/define)
   (define-ffi-definer define-demo (ffi-lib "demo.so"))

   (define _int2int (_fun _int -> _int))

   (define-demo an_int_fun _int2int)
   (define-demo apply_to_twelve (_fun _int2int -> _int))

   (define-cstruct _two_funs
     ([f _int2int]
      [g _int2int]))

   (define-demo apply_two_funs_and_sum
     (_fun _two_funs-pointer _int -> _int))

In that Racket program, `an_int_fun` is a *callout*: a Racket procedure 
that calls foreign code when applied.

   (an_int_fun 5) ;; => 6

If you call `apply_to_twelve` with a Racket procedure, like this:

   (apply_to_twelve add1) ;; => 13

then the FFI converts `add1` (a Racket procedure) into a function 
pointer using the _fun type `_int2int`. Foreign code can use that 
function pointer to *call back* into Racket.

Storing a function in a struct with _int2int fields does the same 
automatic conversion. For example:

   (define (f x) (expt x 2))
   (define (g x) (expt x 3))

   (define tf (make-two_funs #f #f))
   (set-two_funs-f! tf f)
   (set-two_funs-g! tf g)
   ;; or equivalently, (define tf (make-two_funs f g))

   (apply_two_funs_and_sum tf 3) ;; => 36

You can even fetch one of the function fields back and call it, like this:

   ((two_funs-f tf) 4) ;; => 16

IIUC, that creates a callout procedure that calls the callback function 
pointer that calls the original Racket procedure.

I gave `f` and `g` names so they wouldn't be collected, but in general 
you need to make sure a Racket procedure doesn't get collected when 
foreign code still has a callback for it. For example, the following 
code is likely to crash: because the callback function pointer stored in 
tf2->g refers to a Racket procedure that has probably been GC'd:

   (define tf2 (make-two_funs f (lambda (x) (expt x 5))))
   (collect-garbage)
   (apply_two_funs_and_sum tf 3)

See the #:keep argument of `_fun` for more information.

Ryan

-- 
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/[email protected]
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/6c26abc4-09bc-3a2e-369f-b14671e84958%40ccs.neu.edu.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.