2 min read

Bytevectors and Foreign Code

Sometimes when working with bytevectors in Chez Scheme, you want to treat that bytevector as a foreign allocated block of data that you can pass to functions that will be used by the foreign side of code. If the procedure which expects a pointer to a block of memory is already foreign code, that's easy to handle:

(foreign-procedure "my_func" (... u8* ...) ...)

This foreign function will then receive a pointer to the bytevector. Nothing special here, and this is actually a pretty common thing to do. The more annoying case comes the foreign code is actually mapped to a scheme code that needs to treat some of its arguments as return-value arguments. Say for example that you are going to register a foreign callback with a foreign function register_callbacks() that has the following signature:

int64_t callback(char **buf);

Here, the callback is expected to put a pointer to the buffer into its argument, and then return the length as a return value. Ideally, we want to write this callback in Scheme, and we want to have our blocks of data in bytevectors, not in FOREIGN-ALLOC'd pointers that have to be managed manually. However, we can't put a bytevector into a pointer to a pointer to a buffer, because we don't have a means of getting the pointer to the bytevector in Scheme! Or do we?

The trick here is to use a function that will take in a buffer and give you back a pointer to that buffer. It just so happens that memcpy(3) does this:

void *memcpy(void *dst, void *src, size_t count);

We can exploit this fact in Chez Scheme to define something like this:

(define foreign-bytevector-ptr
  (let ([$memcpy (foreign-procedure "memcpy" (u8* uptr unsigned-long) uptr)])
    (lambda (bv)
      (assert (bytevector? bv))
      ($memcpy bv 0 0))))

Voila! Now we can use this to fill our return value argument in our foreign callback that we actually write in Scheme.

(define (callback buf**)
  (let ([bv (get-bv)]
        [bvl (bytevector-length bv)])
    (lock-object bv)
    (foreign-set! 'uptr buf** 0 (foreign-bytevector-ptr bv))
    bvl))

Just remember that you have to lock the bytevector object before you pass it to the foreign side if that vector will be used after the return from the call, because as soon as you re-enter Schemeland, the vector could be relocated by the storage manager. You should also remember to have a way to unlock the object once you know that you no longer need it, so that you don't keep potential memory fragmenters and other efficiency blocks sitting around in the storage manager unnecessarily.