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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/6c26abc4-09bc-3a2e-369f-b14671e84958%40ccs.neu.edu.

Reply via email to