cffi を学ぶ(5) 構造体
cffi を学ぶ(1) 変数と配列 - TIPS
cffi を学ぶ(2) 配列の続きと関数 - TIPS
cffi を学ぶ(3) Cのライブラリとコールバック - TIPS
cffi を学ぶ(4) C のライブラリを使う - TIPS
cffi を学ぶ(5) 構造体 - TIPS
cffi を学ぶ(6) 構造体の続きと translate-*-foreign - TIPS
参考
レファレンス CFFI User Manual
構造体について学んだのでまとめ
cffi では C の構造体はポインタで扱うのが基本
まず C のライブラリを書く
#include <stdio.h> #include <stdlib.h> #include <math.h> typedef struct vec { float x,y,z; } VEC; VEC* ret_vec() { VEC *v = (VEC*)malloc(sizeof(VEC)); v->x = 1.0; v->y = 2.0; v->z = 3.0; return v; } VEC* vec_plus(VEC *v, VEC *u) { VEC *ans = (VEC*)malloc(sizeof(VEC)); ans->x = v->x + u->x; ans->y = v->y + u->y; ans->z = v->z + u->z; return ans; }
これを foo.c としてライブラリを作る
gcc -shared foo.c -o foo.so
以下を評価する
(load "~/quicklisp/setup.lisp") (ql:quickload "cffi") (defpackage :scratch (:use :cl :cffi)) (in-package :scratch) (define-foreign-library libcffi (:darwin "foo.so") (:unix "foo.so")) (use-foreign-library libcffi) (defcstruct vec (x :float) (y :float) (z :float)) ;; ライブラリの関数を定義する (defcfun "ret_vec" (:pointer (:struct vec))) (defcfun "vec_plus" (:pointer (:struct vec)) (v (:pointer (:struct vec))) (u (:pointer (:struct vec)))) ;; 基本はポインタ渡し、返り値もポインタ ;; 構造体のメンバー(スロット)は with-foreign-slots で束縛できる ;; ret-vec (with-foreign-object (ptr '(:struct vec)) (setf ptr (ret-vec)) (with-foreign-slots ((x y z) ptr (:struct vec)) (format t "~@{~a~^ ~}" x y z))) ;; => 1.0 2.0 3.0 ;; vec-plus (with-foreign-objects ((u '(:struct vec)) (v '(:struct vec)) (ans '(:struct vec))) (with-foreign-slots ((x y z) u (:struct vec)) (setf x 1.0 y 1.0 z 1.0)) ;; u = (x 1.0 y 1.0 z 1.0) (with-foreign-slots ((x y z) v (:struct vec)) (setf x 2.0 y 2.0 z 2.0)) ;; v = (x 2.0 y 2.0 z 2.0) (setf ans (vec-plus u v)) ;; ans = (x 3.0 y 3.0 z 3.0) (with-foreign-slots ((x y z) ans (:struct vec)) (format t "~@{~a~^ ~}" x y z))) ;; => 3.0 3.0 3.0
ライブラリによっては構造体を値渡ししたいこともある
素の cffi ではできないので lib-ffi をロードする必要がある
まず取り合えず c のライブラリを書く
#include <stdio.h> #include <stdlib.h> #include <math.h> typedef struct vec { float x,y,z; } VEC; VEC ret_vec() { VEC v; v.x = 1.0; v.y = 2.0; v.z = 3.0; return v; } VEC vec_plus(VEC v, VEC u) { VEC ans; ans.x = v.x + u.x; ans.y = v.y + u.y; ans.z = v.z + u.z; return ans; }
これをコンパイル
gcc -shared bar.c -o bar.so
lisp 側では lib-ffi をロードする。
また構造体は lisp 側では plist として扱うことになる。 C のオブジェクトへの変換、逆変換については lib-ffi がやってくれる。
一度 repl を初期化してから以下を評価する
(load "~/quicklisp/setup.lisp") (ql:quickload "cffi") ;; 構造体の値渡しには cffi-libffi をロードする (ql:quickload "cffi-libffi") (defpackage :scratch (:use :cl :cffi)) ;; ここに libffi を書く必要はないらしい (in-package :scratch) (define-foreign-library bar (:darwin "bar.so") (:unix "bar.so")) (use-foreign-library bar) (defcstruct vec (x :float) (y :float) (z :float)) ;; ライブラリの関数を定義する (defcfun "ret_vec" (:struct vec)) (defcfun "vec_plus" (:struct vec) (v (:struct vec)) (u (:struct vec))) ;; 構造体は plist として返る (ret-vec) ;; => (Z 3.0 Y 2.0 X 1.0) ;; したがって getf でアクセスできる (getf (ret-vec) 'z) ;; => 3.0 ;; 関数の引数には plist を与える (vec-plus '(x 1.0 y 2.0 z 3.0) '(x 10.0 y 10.0 z 10.0)) ;; => (Z 13.0 Y 12.0 X 11.0)
注意
上のように構造体をタイプとして
typedef struct vec {} VEC;
のように書かずに、単に
sturct vec {};
のように書いても、上の例では問題なく動いた。その場合 C 側で関数の引数や返り値の宣言を変更する必要があるが lisp 側では変更の必要はなかった。