用cffi-libffi按值传递结构?

我的印象是CFFI无法通过值传递结构,但CFFI文档说:

要按值传递或返回结构到函数,请加载cffi-libffi系统并将结构指定为(:struct structure-name) 。 要传递或返回指针,可以使用:pointer(:pointer (:struct structure-name))

我正在重新包装cl-opencv函数get-size ,它是这个opencv函数的包装器:

 CvSize cvGetSize(const CvArr* arr) 

因为我不认为当cl-opencv的作者编写库时,CFFI能够通过cffi-libffi系统按值传递结构,所以他必须使用以下所有代码来包装cvGetSize

  (defmacro make-structure-serializers (struct slot1 slot2) "Create a serialization and deserialization function for the structure STRUCT with integer slots SLOT1 and SLOT2. These functions will pack and unpack the structure into an INT64." (let ((pack-fn (intern (concatenate 'string (string struct) (string '->int64)))) (slot1-fn (intern (concatenate 'string (string struct) "-" (string slot1)))) (slot2-fn (intern (concatenate 'string (string struct) "-" (string slot2)))) (unpack-fn (intern (concatenate 'string (string 'int64->) (string struct)))) (make-fn (intern (concatenate 'string (string 'make-) (string struct))))) `(progn (defun ,pack-fn (s) (+ (,slot1-fn s) (ash (,slot2-fn s) 32))) (defun ,unpack-fn (n) (,make-fn ,slot1 (logand n #x00000000ffffffff) ,slot2 (ash n -32)))))) ;; CvSize - Input = (defparameter a (make-size :width 640 :height 480)) Output = #S(SIZE :WIDTH 640 :HEIGHT 480) for ;; following the two. (defstruct size (width 0) (height 0)) (make-structure-serializers :size :width :height) ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" %get-size) :int64 (arr cv-array)) (defun get-size (arr) "Get the dimensions of the OpenCV array ARR. Return a size struct with the dimensions." (let ((nsize (%get-size arr))) (int64->size nsize))) 

鉴于上面引用的CFFI文档,我将如何通过值传递此cvGetSize结构CvSize

我打算更新cl-opencv软件包,我想知道cl-opencv软件包在哪里以及如何按照CFFI文档“加载cffi-libffi系统”,以及“将结构指定为(:struct structure-name)在哪里)” (:struct structure-name) “和”使用:pointer或(:pointer(:struct structure-name))“”传递或返回指针。“

我可以使用上面的cvGetSize包装器使用有关如何执行此操作的详细说明:

 ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" %get-size) :int64 (arr cv-array)) (defun get-size (arr) "Get the dimensions of the OpenCV array ARR. Return a size struct with the dimensions." (let ((nsize (%get-size arr))) (int64->size nsize))) 

编辑@Rörd

我很感激你的反应

我得到了相同的错误…但是出于测试目的,我可以说我将cffi-libffi加载到我当前的会话中(如此输出)

 CL-OPENCV> (asdf:oos 'asdf:load-op :cffi-libffi) # NIL 

它加载所以我只运行你提供的defcfun和defcstruct(带输出):

  CL-OPENCV> (cffi:defcstruct cv-size (width :int) (height :int)) (:STRUCT CV-SIZE) CL-OPENCV> (defcfun ("cvGetSize" %get-size) (:struct cv-size) (arr cv-array)) ; in: DEFCFUN ("cvGetSize" %GET-SIZE) ; ("cvGetSize" CL-OPENCV::%GET-SIZE) ; ; caught ERROR: ; illegal function call ; ; compilation unit finished ; caught 1 ERROR condition Execution of a form compiled with errors. Form: ("cvGetSize" %GET-SIZE) Compile-time error: illegal function call [Condition of type SB-INT:COMPILED-PROGRAM-ERROR] Restarts: 0: [RETRY] Retry SLIME REPL evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [ABORT] Abort thread (#) Backtrace: 0: ((LAMBDA ())) [No Locals] 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV ("cvGetSize" %GET-SIZE) #) Locals: SB-DEBUG::ARG-0 = ("cvGetSize" %GET-SIZE) SB-DEBUG::ARG-1 = # 2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (DEFCFUN ("cvGetSize" %GET-SIZE) (:STRUCT CV-SIZE) (ARR CV-ARRAY)) #) Locals: SB-DEBUG::ARG-0 = (DEFCFUN ("cvGetSize" %GET-SIZE) (:STRUCT CV-SIZE) (ARR CV-ARRAY)) SB-DEBUG::ARG-1 = # 3: (EVAL (DEFCFUN ("cvGetSize" %GET-SIZE) (:STRUCT CV-SIZE) (ARR CV-ARRAY))) Locals: 

我知道libffi安装正确,因为加载了gsll(使用cffi-libffi)我运行gsll测试并且它们都显示在这里(带输出)

 (ql:quickload "lisp-unit") (in-package :gsl) (lisp-unit:run-tests) To load "lisp-unit": Load 1 ASDF system: lisp-unit Loading "lisp-unit" .................................. Unit Test Summary | 4023 assertions total | 4022 passed | 1 failed | 0 execution errors | 0 missing tests # 

它似乎没有用(:struct cv-size)作为问题调用defcfun,因为当我把它称为

 (defcfun ("cvGetSize" %get-size) cv-size (arr cv-array)) 

我得到同样的错误

 Execution of a form compiled with errors. Form: ("cvGetSize" %GET-SIZE) Compile-time error: 

我可以像这样运行我的ipl-image结构

 CL-OPENCV> ;; ;(cffi:foreign-type-size '(:struct ipl-image)) = 144 (cffi:defcstruct ipl-image (n-size :int) (id :int) (n-channels :int) (alpha-channel :int) (depth :int) (color-model :pointer) (channel-seq :pointer) (data-order :int) (origin :int) (align :int) (width :int) (height :int) (roi :pointer) (mask-roi :pointer) (image-id :pointer) (tile-info :pointer) (image-size :int) (image-data :string) (width-step :int) (border-mode :pointer) (border-const :pointer) (image-data-origin :string)) output>(:STRUCT IPL-IMAGE) 

和我的create-image包装器现在加载cffi-libffi并且你的(:struct ipl-image)在它上运行正常但是…显示输出

 ;; IplImage* cvCreateImage(CvSize size, int depth, int channels) (cffi:defcfun ("cvCreateImage" %create-image) (:struct ipl-image) (size :int64) (depth :int) (channels :int)) (defun create-image (size depth channels) "Create an image with dimensions given by SIZE, DEPTH bits per channel, and CHANNELS number of channels." (let ((nsize (size->int64 size))) (%create-image nsize depth channels))) 

CREATE-IMAGE

但是当我跑

 (defparameter img-size (make-size :width 640 :height 480)) (defparameter img (create-image img-size +ipl-depth-8u+ 1)) 

在repl创建一个图像没有任何事情,repl只是挂起…

但是当我使用ipl-image而不是(:struct ipl-image)运行create image wrapper时

我可以运行:

  (defparameter img-size (make-size :width 640 :height 480)) (defparameter img (create-image img-size +ipl-depth-8u+ 1)) 

罚款然后运行它来访问struct值(带输出)

  (cffi:with-foreign-slots (( n-size id n-channels alpha-channel depth color-model channel-seq data-order origin align width height roi mask-roi image-id tile-info image-size image-data width-step border-mode border-const image-data-origin) img (:struct ipl-image)) (cffi:mem-ref img :char ) (format t "n-size ~a ~%" n-size) (format t "id ~a ~%" id) (format t "n-channels ~a ~%" n-channels) (format t "alpha-channel ~a ~%" alpha-channel) (format t "depth ~a ~%" depth) (format t "color-model ~a ~%" color-model) (format t "channel-seq ~a ~%" channel-seq) (format t "data-order ~a ~%" data-order) (format t "origin ~a ~%" origin) (format t "align ~a ~%" align) (format t "width ~a ~%" width) (format t "height ~a ~%" height) (format t "roi ~a ~%" roi) (format t "mask-roi ~a ~%" mask-roi) (format t "image-id ~a ~%" image-id) (format t "tile-info ~a ~%" tile-info) (format t "image-size ~a ~%" image-size) (format t "image-data ~a ~%" image-data) (format t "width-step ~a ~%" width-step) (format t "border-mode ~a ~%" border-mode) (format t "border-const ~a ~%" border-const) (format t "image-data-origin ~a ~%" image-data-origin)) output> n-size 144 id 0 n-channels 1 alpha-channel 0 depth 8 color-model #.(SB-SYS:INT-SAP #X59415247) channel-seq #.(SB-SYS:INT-SAP #X400000000) data-order 640 origin 480 align 0 width 0 height 0 roi #.(SB-SYS:INT-SAP #X00000000) mask-roi #.(SB-SYS:INT-SAP #X00000000) image-id #.(SB-SYS:INT-SAP #X0004B000) tile-info #.(SB-SYS:INT-SAP #X7FFFF7F04020) image-size 640 image-data NIL width-step 0 border-mode #.(SB-SYS:INT-SAP #X00000000) border-const #.(SB-SYS:INT-SAP #X00000000) image-data-origin 

但是我没有按价值获得结构

  color-model #.(SB-SYS:INT-SAP #X59415247) 

当我用c输入那个值img-> colorModel时

 IplImage* img=cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 3); cout << "colorModel = " << endl << " " <colorModel << endl < colorModel = RGB 

所以任何帮助将不胜感激

好的1更多编辑:

我再试一次,它在这里工作是我的输出

  CL-OPENCV> (asdf:oos 'asdf:load-op :cffi-libffi) # NIL CL-OPENCV> ;; ;(cffi:foreign-type-size '(:struct cv-size)) = 8 (cffi:defcstruct cv-size (width :int) (height :int)) ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" %get-size) (:struct cv-size) (arr cv-array)) STYLE-WARNING: redefining CL-OPENCV::%GET-SIZE in DEFUN %GET-SIZE CL-OPENCV> (defparameter img-size (make-size :width 640 :height 480)) (defparameter img (create-image img-size +ipl-depth-8u+ 1)) IMG CL-OPENCV> (defparameter size (%get-size img)) SIZE CL-OPENCV> size (HEIGHT 480 WIDTH 640) CL-OPENCV> 

不知道我第一次做错了什么,但是…如果你能检查我的结果并validation我刚刚通过价值传递结构我会永远感激

谢谢罗德

如果您仍然有兴趣帮助我调试Rord,那么另一个编辑

如果得到错误:

  The value (HEIGHT 480 WIDTH 640) is not of type SB-SYS:SYSTEM-AREA-POINTER. [Condition of type TYPE-ERROR] 

这是导致它的历史记录(这是在我发布了prev。编辑之后直接发生的,所以我的emacs已经加载了所有上一代编辑代码):

  CL-OPENCV> (defun get-size (arr) "Get the dimensions of the OpenCV array ARR. Return a size struct with the dimensions." (cffi:with-foreign-slots ((width height) (%get-size arr) (:struct cv-size)) (make-size :width width :height height))) STYLE-WARNING: redefining CL-OPENCV:GET-SIZE in DEFUN GET-SIZE CL-OPENCV> (defparameter img-size (make-size :width 640 :height 480)) (defparameter img (create-image img-size +ipl-depth-8u+ 1)) IMG CL-OPENCV> (defparameter size (get-size img)) The value (HEIGHT 480 WIDTH 640) is not of type SB-SYS:SYSTEM-AREA-POINTER. [Condition of type TYPE-ERROR] 

我明白了,因为:

 (defparameter size (get-size img)) 

访问你的defun …我跟踪它所以当我刚刚运行 – 显示输出:

 CL-OPENCV> ;; ;(cffi:foreign-type-size '(:struct cv-size)) = 8 (cffi:defcstruct cv-size (width :int) (height :int)) ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" %get-size) (:struct cv-size) (arr cv-array)) STYLE-WARNING: redefining CL-OPENCV::%GET-SIZE in DEFUN %GET-SIZE CL-OPENCV> (defparameter capture (create-camera-capture 0)) CAPTURE CL-OPENCV> (defparameter frame (query-frame capture)) FRAME CL-OPENCV> (defparameter size (%get-size frame)) SIZE CL-OPENCV> size (HEIGHT 480 WIDTH 640) CL-OPENCV> (cffi:with-foreign-slots ((width height) size (:struct cv-size)) (list width height )) 

我收到错误:

 The value (HEIGHT 480 WIDTH 640) is not of type SB-SYS:SYSTEM-AREA-POINTER. [Condition of type 

我认为这是因为你的defcfun的输出只是一个列表而且with-foreign-slot需要一个指针

我跑了这个:

 (HEIGHT 480 WIDTH 640) CL-OPENCV> (first size) HEIGHT 

validation它只是一个列表

顺便说一句,我用这些function进行测试

 (defparameter capture (create-camera-capture 0)) (defparameter frame (query-frame capture)) 

因为它有一个更纯粹的输出… create-image使用了get-size的hackery i oroginally发布在这个顶部?

我想使用create-image和get-size w / o所有的hackery只是使用结构返回所以我可以停止使用make-size并使它更纯净….所以任何建议都会是黄金……这就是我想要创造图像的方式…我只需要接受你(Rord’s)defcfun的输出……我现在要把你的defcfun输出转换((高) 480 WIDTH 640))指针…所以它将在此运行

 ;; IplImage* cvCreateImage(CvSize size, int depth, int channels) (cffi:defcfun ("cvCreateImage" %create-image) ipl-image (size cv-size) (depth :int) (channels :int)) 

或整个大小的东西是必要的…

我也改变了你加入的defun

 (defun get-size (arr) "Get the dimensions of the OpenCV array ARR. Return a size struct with the dimensions." (setf arr (%get-size arr)) (make-size :width (cadddr arr) :height (cadr arr))) 

它现在有效…如果我弄乱了什么,如果你的defun会更好,仍然很好玩

编辑!!!!我把它全部搞定!!!!!

 here is the repl output : ; SLIME 2012-05-25 CL-OPENCV> ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" get-size) (:pointer (:struct cv-size)) (arr cv-array)) STYLE-WARNING: redefining CL-OPENCV:GET-SIZE in DEFUN GET-SIZE CL-OPENCV> ;; IplImage* cvCreateImage(CvSize size, int depth, int channels) (cffi:defcfun ("cvCreateImage" create-image) (:pointer (:struct ipl-image)) (size (:pointer (:struct ipl-image))) (depth :int) (channels :int)) STYLE-WARNING: redefining CL-OPENCV:CREATE-IMAGE in DEFUN CREATE-IMAGE CL-OPENCV> (defun detect-red-objects (&optional (camera-index 0)) "Uses IN-RANGE-SCALAR to detect red objects" (with-capture (capture (create-camera-capture camera-index)) (let ((window-name-1 "Video") (window-name-2 "Ball")) (named-window window-name-1) (named-window window-name-2) (move-window window-name-1 290 225) (move-window window-name-2 940 225) (do* ((frame (query-frame capture) (query-frame capture)) (img (clone-image frame)) (frame (clone-image img)) (img-size (get-size frame)) (img-hsv (create-image img-size +ipl-depth-8u+ 3)) (img-hsv-size (get-size img-hsv)) (img-thresh (create-image img-hsv-size +ipl-depth-8u+ 1)) (scalar-1 (make-cv-scalar 170.0 160.0 60.0)) (scalar-2 (make-cv-scalar 180.0 256.0 256.0))) ((plusp (wait-key *millis-per-frame*)) nil) (smooth frame frame +gaussian+ 3 3) (cvt-color frame img-hsv +bgr2hsv+) (in-range-s img-hsv scalar-1 scalar-2 img-thresh) (smooth img-thresh img-thresh +gaussian+ 3 3) (show-image window-name-1 frame) (show-image window-name-2 img-thresh)) (destroy-all-windows)))) DETECT-RED-OBJECTS (the function detect-red-objects runs btw!... 

编辑!!!!我把它全部展开!!!!! ……部分…. II – 更好!

 I messed up the struct on create-image the first time but it still ran...weird...but it runs when put the create-image struct back to cv-size....so no prob there...here is revised repl output ; SLIME 2012-05-25 CL-OPENCV> ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" get-size) (:pointer (:struct cv-size)) (arr cv-array)) STYLE-WARNING: redefining CL-OPENCV:GET-SIZE in DEFUN GET-SIZE CL-OPENCV> ;; IplImage* cvCreateImage(CvSize size, int depth, int channels) (cffi:defcfun ("cvCreateImage" create-image) (:pointer (:struct ipl-image)) (size (:pointer (:struct cv-size))) (depth :int) (channels :int)) STYLE-WARNING: redefining CL-OPENCV:CREATE-IMAGE in DEFUN CREATE-IMAGE CL-OPENCV> (defun detect-red-objects (&optional (camera-index 0)) "Uses IN-RANGE-SCALAR to detect red objects" (with-capture (capture (create-camera-capture camera-index)) (let ((window-name-1 "Video") (window-name-2 "Ball")) (named-window window-name-1) (named-window window-name-2) (move-window window-name-1 290 225) (move-window window-name-2 940 225) (do* ((frame (query-frame capture) (query-frame capture)) (img (clone-image frame)) (frame (clone-image img)) (img-size (get-size frame)) (img-hsv (create-image img-size +ipl-depth-8u+ 3)) (img-hsv-size (get-size img-hsv)) (img-thresh (create-image img-hsv-size +ipl-depth-8u+ 1)) (scalar-1 (make-cv-scalar 170.0 160.0 60.0)) (scalar-2 (make-cv-scalar 180.0 256.0 256.0))) ((plusp (wait-key *millis-per-frame*)) nil) (smooth frame frame +gaussian+ 3 3) (cvt-color frame img-hsv +bgr2hsv+) (in-range-s img-hsv scalar-1 scalar-2 img-thresh) (smooth img-thresh img-thresh +gaussian+ 3 3) (show-image window-name-1 frame) (show-image window-name-2 img-thresh)) (destroy-all-windows)))) DETECT-RED-OBJECTS 

@Liam编辑

好吧,我尝试了你的trans-from-foreign方法,它确实有效,我在structs.lisp文件中定义了这些方法

 (cffi:defcstruct (cv-size :class cv-size-type) (width :int) (height :int)) (defmethod cffi:translate-from-foreign (p (type cv-size-type)) (let ((plist (call-next-method))) (make-size :width (getf plist 'width) :height (getf plist 'height)))) 

并且get-size和create-image定义如下

 ;; CvSize cvGetSize(const CvArr* arr) (cffi:defcfun ("cvGetSize" get-size) (:struct cv-size) (arr cv-arr)) ;; IplImage* cvCreateImage(CvSize size, int depth, int channels) (cffi:defcfun ("cvCreateImage" %create-image) ipl-image (size :int64) (depth :int) (channels :int)) (defun create-image (size depth channels) "Create an image with dimensions given by SIZE, DEPTH bits per channel, and CHANNELS number of channels." (let ((nsize (size->int64 size))) (%create-image nsize depth channels))) 

这是size-> int64的定义

  (DEFUN SIZE->INT64 (S) (+ (SIZE-WIDTH S) (ASH (SIZE-HEIGHT S) 32))) 

但我喜欢翻译外国defmethod的想法

所以我想知道你是否可以向我展示如何从方法制作下面的翻译成外国版本这真的会让我的图书馆唱歌……我的目标是为opencv制作一个完整的cffi包装和gsll一样好是gsl所以这真的有助于更快地发生….再次感谢你对这一切的帮助到目前为止

 (defmethod cffi:translate-from-foreign (p (type cv-size-type)) (let ((plist (call-next-method))) (make-size :width (getf plist 'width) :height (getf plist 'height)))) 

要“加载cffi-libffi系统”,您需要在库的.asd文件中将其指定为依赖项。 (注意:cffi-libffi需要在您的系统上安装C库libffi。)

为了能够使用(:struct structure-name) ,你需要首先用cffi:defcstruct定义结构,就像这样(我假设cffi:defcstructcffi:defcfuncffi:with-foreign-slots是在当前包中导入):

 (defcstruct cv-size (width :int) (height :int)) 

然后你可以在defcfun使用(:struct cv-size) ,如下所示:

 (defcfun ("cvGetSize" %get-size) (:struct cv-size) (arr cv-array)) 

编辑 :修复了传递值结构的get-size

最后像这样定义get-size

 (defun get-size (arr) "Get the dimensions of the OpenCV array ARR. Return a size struct with the dimensions." (let ((%size (%get-size arr))) (make-size :width (getf %size 'width) :height (getf %size 'height)))) 

编辑2

如果我正确理解Liam的答案,那么就是如何编写一个translate-from-foreign方法,以便它直接创建结构,而不创建中间plist:

 (defmethod cffi:translate-from-foreign (p (type cv-size-type)) (with-foreign-slots ((width height) p (:struct cv-size)) (make-size :width width :height height))) 

您不必定义%get-sizeget-size 。 相反,你可以定义

 (defcstruct (cv-size :class cv-size-type) (width :int) (height :int)) (defmethod cffi:translate-from-foreign (p (type cv-size-type)) (let ((plist (call-next-method))) (make-size :width (getf plist 'width) :height (getf plist 'height)))) 

然后使用defcfun直接定义函数get-size

这样做的好处是,只要你有一个返回cv-size的函数,它就会自动被翻译。 并且,如果您要传递cv-size的外部函数,请为cffi:translate-into-foreign-memory定义适当的方法。 如果在指向结构的指针上使用mem-aref,您也将自动获得翻译。 或者,如果你打电话给cffi:convert-from-foreign。

在这个例子中,我使用了默认的plist转换方法; 如果你希望直接访问插槽而不需要调用(call-next-method),你可以。

(顺便说一下,你之所以认为CFFI无法按价值传递结构的原因是它直到最近才能通过; cffi-libffi是在0.11.0版本的基础上推出的。)

你只需要添加:class xxx to cffi:defcstruct then (cffi:defmethod translate-into-foreign-memory (object (type xxx) pointer) yyyy) ,它会自动将值传递给外部函数!! 惊人!!

并且(cffi:defmethod translate-from-foreign (pointer (type xxx)) zzzz)会将返回的结构数据转换为lisp数据。

更多信息,请查看我的答案 ^ _ ^