Hello Everyone,
I'm creating an FFI that requires writing to and reading from a C (char *) buffer. The buffer may contain arbitrary binary data (not necessarily a character string). I wrote a few procedures (see below) to copy the data to and from a u8vector so I can operate on it using ports.
I'm hoping for comments or suggestions from you all about the code. Is there a cleaner/better/faster way to do this? Ideally there would be some way to open a byte-port directly to the buffer so it wouldn't have to be copied. Is this possible?
Thanks in advance,
-Ben
;; Typical use of this code:
(with-input-from-u8vector (c-buffer->u8vector foreign-pointer-to-pointer 0 buffer-size ) (lambda () ...))
(u8vector->c-buffer (with-output-to-u8vector '(char-encoding: ASCII) (lambda () ...)) foreign-pointer-to-buffer 0 buffer-size)
;; The code
(declare (fixnum) (not safe))
(c-declare #<<eo-declare
typedef const char * const_char_ptr;
eo-declare )
(c-define-type const-char-buffer "const_char_ptr") (c-define-type size-type unsigned-int32) ; Using -D_FILE_OFFSET_BITS=64 makes off_t 8 bytes instead of 4 (c-define-type offset-type "off_t" "S64_TO_SCMOBJ" "SCMOBJ_TO_S64" #f)
(define u8vector->c-buffer (c-lambda (scheme-object (pointer char) offset-type size-type) void #<<end-lambda
off_t i; ___U8 c; for (i = 0; i < ___arg4; i++) { ___SCMOBJ_to_U8(___U8VECTORREF(___arg1, ___FIX(i)), &c, i); ___CAST(char *,___arg2)[i + ___arg3] = c; } end-lambda ))
(define %c-buffer->u8vector (c-lambda (const-char-buffer scheme-object offset-type size-type) void #<<end-lambda
off_t i; ___SCMOBJ c; ___U8* buff = ___CAST(___U8*, ___arg1); for (i = 0; i < ___arg4; i++) { ___U8_to_SCMOBJ(buff[i + ___arg3], &c, i); ___U8VECTORSET(___arg2, ___FIX(i), c); }
end-lambda ))
(define (c-buffer->u8vector buff offset size) (let ((v (make-u8vector size))) (%c-buffer->u8vector buff v offset size) v))
Afficher les réponses par date
Hi Ben,
Ben Weaver wrote:
Hello Everyone,
I'm creating an FFI that requires writing to and reading from a C (char *) buffer. The buffer may contain arbitrary binary data (not necessarily a character string). I wrote a few procedures (see below) to copy the data to and from a u8vector so I can operate on it using ports.
I'm hoping for comments or suggestions from you all about the code. Is there a cleaner/better/faster way to do this? Ideally there would be some way to open a byte-port directly to the buffer so it wouldn't have to be copied. Is this possible?
Here's my attempt (below).
N.B. I'm going out on a limb here because I'm certainly no expert on gambit internals - this is just from looking at the mem.c and gambit.h files. Also I'm not 100% sure the technique is portable to architectures that don't provide byte-addressable memory (e.g. ARM - can somebody confirm this?).
That said, here's the code (hopefully somebody else will point out if I've gone wrong). The 'create-still-u8vector' ensures that the u8vector won't be moved by the garbage collector and reduces the refcount so that it'll be properly garbage collected. 'put-some-bytes-in-still-u8vector' shows how to convert the vector to an addressable array (e.g. for loading files into etc..).
Hope this helps!
Phil
(define create-still-u8vector (c-lambda (int) scheme-object #<<c-lambda-end ___result = ___alloc_scmobj(___sU8VECTOR, ___arg1, ___STILL); ___EXT(___release_scmobj) (___result); c-lambda-end ))
(define put-some-bytes-in-still-u8vector (c-lambda (scheme-object) void #<<c-lambda-end char *a; a = (char *)(___BODY_AS(___arg1,___tSUBTYPED)); a[0] = 'h'; a[1] = 'e'; a[2] = 'l'; a[3] = 'l'; a[4] = 'o'; c-lambda-end ))
Thanks for your help, Phil. Combining your suggestions with some ideas from gamsock, I now have this code. In addition to being significantly faster, it will also work with any vector type (I think).
Best wishes,
-Ben
;; code
(define vector->c-buffer (c-lambda (scheme-object scheme-object size-type) void #<<end-lambda
if (!___TESTSUBTYPE(___arg2, ___sFOREIGN)) return ___FIX(___STOC_POINTER_ERR + 2);
memcpy(___CAST(void *, ___FIELD(___arg2, ___FOREIGN_PTR)), ___CAST(void *, ___BODY_AS(___arg1, ___tSUBTYPED)), ___arg3);
end-lambda ))
(define %c-buffer->vector (c-lambda (scheme-object scheme-object size-type) void #<<end-lambda
if (!___TESTSUBTYPE(___arg1, ___sFOREIGN)) return ___FIX(___STOC_POINTER_ERR + 1);
memcpy(___CAST(void *, ___BODY_AS(___arg2, ___tSUBTYPED)), ___CAST(void *, ___FIELD(___arg1, ___FOREIGN_PTR)), ___arg3);
end-lambda ))