是否可以将字节向量传递给 C 函数而不在 SBCL 中复制

xie*_*pan 5 sbcl common-lisp ffi

我正在尝试使用 SBCL 的 FFI 工具调用 C 函数。C 函数需要字节缓冲区(无符号字符)并处理字节数据。不幸的是,在大多数情况下,缓冲区非常大。我想知道我是否已经有一个 Lisp 字节向量(simple-array元素类型为 '(unsigned-byte 8)),是否可以将其传递给 C 函数,而不是分配外来缓冲区并在那里复制字节。我认为这是一个经常出现的场景,也许 SBCL 团队已经涵盖了这一点。

我查看了 SBCL 手册,它只指出sb-alien:c-string类型可以直接传递给 C 函数而不进行复制,并且它要求字符为“base-char”(在 0 ~ 127 范围内,支持 unicode)。我们也可以在不复制的情况下传递字节向量,这不是很好吗?

ign*_*ens 3

我不知道这在SBCL中是否可行。为了使其成为可能,至少需要以 GC 不移动数组的方式分配数组。SBCL 手册包含对可能执行此操作的宏的引用sb-sys:with-pinned-objects,但那里似乎没有任何其他信息。

然而,当我需要做这样的事情时,我会向后做:在 C 级别创建一个合适的对象,然后用 Lisp 代码包装它来访问它。这样做的优点是你知道如何从 C 语言中处理它,缺点是它不是数组,所以你必须编写自己的访问函数:诸如此类的东西read-sequence不起作用等等。

我最终使用了包装外来向量的结构:这是我用来处理有符号字节数组的代码块:

(deftype index ()
  `(integer 0 (,most-positive-fixnum)))

...

(defstruct (foreign-octet-vector
            (:constructor %make-foreign-octet-vector (octets size)))
  (octets (error "no")
   :type (alien (* (signed 8)))
   :read-only t)
  (size 0
   :type index
   :read-only t))

(defun make-foreign-octet-vector (size)
  (%make-foreign-octet-vector
   (make-alien (signed 8) size)
   size))

(defun free-foreign-octet-vector (v)
  (free-alien (foreign-octet-vector-octets v))
  nil)

(declaim (inline fov-ref (setf fov-ref)))

(defun fov-ref (v n)
  (declare (type foreign-octet-vector v)
           (type index n))
  (if (< n (foreign-octet-vector-size v))
      (deref (foreign-octet-vector-octets v) n)
      (error "out of range")))

(defun (setf fov-ref) (value v n)
  (declare (type foreign-octet-vector v)
           (type index n)
           (type (signed-byte 8) value))
  (if (< n (foreign-octet-vector-size v))
      (setf (deref (foreign-octet-vector-octets v) n) value)
      (error "out of range")))
Run Code Online (Sandbox Code Playgroud)

最重要的是,有一个相当明显的宏用于unwind-protect确保事物被释放。

我不知道这是否是特殊的 SBCL 代码,或者实际上总体上是正确的,但它对我有用。CFFI 可能为这一切提供了一些更好的接口。