我正在尝试使用其 FFI 工具从 SBCL 调用 C 函数。 C 函数需要字节缓冲区(无符号字符)并处理字节数据。不幸的是,在大多数情况下,缓冲区非常大。我想知道我是否已经有一个 Lisp 字节向量(元素类型为 '(unsigned-byte 8) 的
simple-array
),是否可以将其传递给 C 函数,而不是分配外来缓冲区并复制字节那里。我认为这是一个经常出现的场景,也许 SBCL 团队已经涵盖了这一点。
我查看了 SBCL 手册,它只指出
sb-alien:c-string
类型可以直接传递给 C 函数而不进行复制,并且它要求字符为“base-char”(在 0 ~ 127 范围内,支持 unicode)。我们也可以在不复制的情况下传递字节向量,这不是很好吗?
我不知道这在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")))
除此之外,还有一个相当明显的宏,使用
unwind-protect
来确保事物被释放。
我不知道这是否是特殊的 SBCL 代码,或者实际上总体上是正确的,但它对我有用。 CFFI 可能为这一切提供了一些更好的接口。
sb-kernel:get-lisp-obj-address
,并且您可能还需要
sb-sys:with-pinned-objects
。另一个问题是它的想法有多明智(可移植性、sbcl 中数组布局的内部变化......)。肮脏的例子(忽略固定,long vs. int,...):
让我们创建一个使用 Lisp 数组的函数:
extern char test(const char *t, int idx)
{
return t[idx];
}
然后(在创建并加载共享库之后)我们可以将向量的地址作为整数传递:
(sb-alien:alien-funcall
(sb-c-call:extern-alien "test"
(function sb-alien:int sb-alien:unsigned-long sb-alien:unsigned-long))
(sb-kernel:get-lisp-obj-address (make-array 5 :element-type '(unsigned-byte 8) :initial-contents '(10 20 30 40 50)))
3)
将返回第三个元素(而不是第四个元素,因为从零开始)。对象有点复杂。是的,内容发生了变化,因为 lisp 对象有点复杂。例如,您可以在 -7 地址处获取数组大小(标记)。