CFFI 炼丹:用“点”化繁为简 🧙♂️ 2024-09-19 作者 C3P00 引言 CFFI,这个强大的工具,让我们在 Lisp 中调用 C 函数,如同驾驭风火轮,穿梭于两个世界。然而,使用 CFFI 的 API 编写 C 风格代码,有时却像是在泥潭中跋涉,因为你需要不断地传递类型信息,而 C 中的“点”运算符却拥有着神奇的类型推断能力,让我们可以轻松地访问结构体成员。 为了让 CFFI 也能像 C 一样优雅,我们打造了 cffi-ops 这个炼丹炉,它将 CFFI 的繁琐操作,炼化为简洁的“点”操作,让你在 Lisp 中写 C 代码,如同行云流水般流畅。 炼丹秘籍:规则与对比 cffi-ops 的炼丹秘籍,就是将 C 中的“点”运算符,映射到 Lisp 中的宏定义,让 Lisp 代码的结构与 C 代码保持一致。 C 语法cffi-ops 语法x->y.z 或 x->y->z(-> x y z) (注意:x、y 和 z 必须与 defcstruct 中定义的符号相同)&x->y(& (-> x y))*x([] x)x[n]([] x n)&x[n] 或 x + n(& ([] x n))x.y = z(setf (-> x y) z) 如果 z 是一个变量(csetf (-> x y) z) 如果 z 是一个 CFFI 指针A _a, *a = &_a(clet ((a (foreign-alloca '(:struct A. ))) ...)✅A *a = malloc(sizeof(A. )✅(clet ((a (cffi:foreign-alloc '(:struct A. ))) ...)✅A _a = *b, *a = &_a(clet ((a ([] b))) ...)A *a = b(clet ((a b)) ...) 炼丹炉的奥妙:CFFI 指针与 clet 在 Lisp 中,我们无法直接操作 C 的复合类型,因此,绑定和赋值复合类型需要借助 clet (或 clet*) 和 csetf,它们作用于 CFFI 指针,实现对 C 数据的操控。 炼丹师的助手:arrow-macros cffi-ops 的炼丹炉,是建立在 arrow-macros 的基础上,它提供了 -> 宏,让我们可以像在 C 中一样,轻松地访问结构体成员。因此,cffi-ops 与 arrow-macros 相辅相成,让你在 Lisp 中编写 C 代码,更加得心应手。 炼丹实例:向量加法 让我们来看一个 C 代码的例子: #include <stdlib.h> #include <assert.h> typedef struct { float x; float y; float z; } Vector3; typedef struct { Vector3 v1; Vector3 v2; Vector3 v3; } Matrix3; void Vector3Add(Vector3 *output, const Vector3 *v1, const Vector3 *v2) { output->x = v1->x + v2->x; output->y = v1->y + v2->y; output->z = v1->z + v2->z; } int main(int argc, char *argv[]) { Matrix3 m1[3]; m1[0].v1.x = 1.0; m1[0].v1.y = 2.0; m1[0].v1.z = 3.0; Matrix3 m2 = *m1; Vector3 *v1 = &m2.v1; Vector3 *v2 = malloc(sizeof(Vector3)); ,*v2 = *v1; v2->x = 3.0; v2->z = 1.0; Vector3Add(v1, v1, v2); assert(v1->x == 4.0); assert(v1->y == 4.0); assert(v1->z == 4.0); free(v2); return 0; } 使用 cffi-ops,我们可以将其改写为 Lisp 代码: (defpackage cffi-ops-example (:use #:cl #:cffi #:cffi-ops)) (in-package #:cffi-ops-example) (defcstruct vector3 (x :float) (y :float) (z :float)) (defcstruct matrix3 (v1 (:struct vector3)) (v2 (:struct vector3)) (v3 (:struct vector3))) (defun vector3-add (output v1 v2) (clocally (declare (ctype (:pointer (:struct vector3)) output v1 v2)) (setf (-> output x) (+ (-> v1 x) (-> v2 x)) (-> output y) (+ (-> v1 y) (-> v2 y)) (-> output z) (+ (-> v1 z) (-> v2 z))))) (defun main () (clet ((m1 (foreign-alloca '(:array (:struct matrix3) 3)))) (setf (-> ([] m1 0) v1 x) 1.0 (-> ([] m1 0) v1 y) 2.0 (-> ([] m1 0) v1 z) 3.0) (clet* ((m2 ([] m1)) (v1 (& (-> m2 v1))) (v2 (foreign-alloc '(:struct vector3)))) (csetf ([] v2) ([] v1)) (setf (-> v2 x) 3.0 (-> v2 z) 1.0) (vector3-add v1 v1 v2) (assert (= (-> v1 x) 4.0)) (assert (= (-> v1 y) 4.0)) (assert (= (-> v1 z) 4.0)) (foreign-free v2)))) 而没有使用 cffi-ops 的 Lisp 代码则更加冗长: (defpackage cffi-example (:use #:cl #:cffi)) (in-package #:cffi-example) (defcstruct vector3 (x :float) (y :float) (z :float)) (defcstruct matrix3 (v1 (:struct vector3)) (v2 (:struct vector3)) (v3 (:struct vector3))) (declaim (inline memcpy)) (defcfun "memcpy" :void (dest :pointer) (src :pointer) (n :size)) (defun vector3-add (output v1 v2) (with-foreign-slots (((xout x) (yout y) (zout z)) output (:struct vector3)) (with-foreign-slots (((x1 x) (y1 y) (z1 z)) v1 (:struct vector3)) (with-foreign-slots (((x2 x) (y2 y) (z2 z)) v2 (:struct vector3)) (setf xout (+ x1 x2) yout (+ y1 y2) zout (+ z1 z2)))))) (defun main () (with-foreign-object (m1 '(:struct matrix3) 3) (with-foreign-slots ((x y z) (foreign-slot-pointer (mem-aptr m1 '(:struct matrix3) 0) '(:struct matrix3) 'v1) (:struct vector3)) (setf x 1.0 y 2.0 z 3.0)) (with-foreign-object (m2 '(:struct matrix3)) (memcpy m2 m1 (foreign-type-size '(:struct matrix3))) (let ((v1 (foreign-slot-pointer m2 '(:struct matrix3) 'v1)) (v2 (foreign-alloc '(:struct vector3)))) (memcpy v2 v1 (foreign-type-size '(:struct vector3))) (with-foreign-slots ((x z) v2 (:struct vector3)) (setf x 3.0 z 1.0)) (vector3-add v1 v1 v2) (with-foreign-slots ((x y z) v1 (:struct vector3)) (assert (= x 4.0)) (assert (= y 4.0)) (assert (= z 4.0))) (foreign-free v2))))) 两种代码在 SBCL 上生成几乎相同的机器码,性能也十分接近。 总结 cffi-ops 为我们提供了一种简洁高效的方式,让我们在 Lisp 中编写 C 代码,如同在 C 中一样自然流畅。它将 CFFI 的复杂操作,炼化为简洁的“点”操作,让我们可以专注于代码的逻辑,而不用被繁琐的类型信息所困扰。 参考文献 cffi-ops arrow-macros CFFI
引言
CFFI,这个强大的工具,让我们在 Lisp 中调用 C 函数,如同驾驭风火轮,穿梭于两个世界。然而,使用 CFFI 的 API 编写 C 风格代码,有时却像是在泥潭中跋涉,因为你需要不断地传递类型信息,而 C 中的“点”运算符却拥有着神奇的类型推断能力,让我们可以轻松地访问结构体成员。
为了让 CFFI 也能像 C 一样优雅,我们打造了
cffi-ops
这个炼丹炉,它将 CFFI 的繁琐操作,炼化为简洁的“点”操作,让你在 Lisp 中写 C 代码,如同行云流水般流畅。炼丹秘籍:规则与对比
cffi-ops
的炼丹秘籍,就是将 C 中的“点”运算符,映射到 Lisp 中的宏定义,让 Lisp 代码的结构与 C 代码保持一致。cffi-ops
语法x->y.z
或x->y->z
(-> x y z)
(注意:x
、y
和z
必须与defcstruct
中定义的符号相同)&x->y
(& (-> x y))
*x
([] x)
x[n]
([] x n)
&x[n]
或x + n
(& ([] x n))
x.y = z
(setf (-> x y) z)
如果z
是一个变量(csetf (-> x y) z)
如果z
是一个 CFFI 指针A _a, *a = &_a
(clet ((a (foreign-alloca '(:struct A. ))) ...)✅
A *a = malloc(sizeof(A. )✅
(clet ((a (cffi:foreign-alloc '(:struct A. ))) ...)✅
A _a = *b, *a = &_a
(clet ((a ([] b))) ...)
A *a = b
(clet ((a b)) ...)
炼丹炉的奥妙:CFFI 指针与
clet
在 Lisp 中,我们无法直接操作 C 的复合类型,因此,绑定和赋值复合类型需要借助
clet
(或clet*
) 和csetf
,它们作用于 CFFI 指针,实现对 C 数据的操控。炼丹师的助手:
arrow-macros
cffi-ops
的炼丹炉,是建立在arrow-macros
的基础上,它提供了->
宏,让我们可以像在 C 中一样,轻松地访问结构体成员。因此,cffi-ops
与arrow-macros
相辅相成,让你在 Lisp 中编写 C 代码,更加得心应手。炼丹实例:向量加法
让我们来看一个 C 代码的例子:
使用
cffi-ops
,我们可以将其改写为 Lisp 代码:而没有使用
cffi-ops
的 Lisp 代码则更加冗长:两种代码在 SBCL 上生成几乎相同的机器码,性能也十分接近。
总结
cffi-ops
为我们提供了一种简洁高效的方式,让我们在 Lisp 中编写 C 代码,如同在 C 中一样自然流畅。它将 CFFI 的复杂操作,炼化为简洁的“点”操作,让我们可以专注于代码的逻辑,而不用被繁琐的类型信息所困扰。参考文献