読者です 読者をやめる 読者になる 読者になる

cffi を学ぶ(2) 配列の続きと関数

lisp

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
日本語の入門サイト http://cl.cddddr.org/index.cgi?CFFI

前提として quicklisp で cffi を入れたものとして、以下を repl で評価しておく。

(load "~/quicklisp/setup.lisp")
(ql:quickload "cffi")

(defpackage :scratch
  (:use :cl :cffi))

(in-package :scratch)

memcpy を使いたいので先に size_t を定義してやる。

(defctype size_t 
    #+x86-64 :uint64
    #-x86-64 :uint32)

組み込みの変数 *features* の中に項目があれば #+ の項が評価され、なければ #- の項が評価される。単純に *features* の中身をみて x86-64 があれば、

(defctype size_t :uint64)

としてもいい。詳細は上記サイト参照。

memcpy を定義する。

(defcfun "memcpy" :pointer
  (dest :pointer) (src :pointer) (len size_t))

defcfun は関数名 返り値の型 (引数とその型) を並べる。もし C の名前と Lisp の名前を変えたければ

(defcfun ("hoge" lisp-hoge) :type
  (arg :type) ... ) 

のように (Cの関数名 Lisp名) とする。

*src* を要素がすべて1.0の配列として、それを *dest* にコピーする。

(defparameter *src* (foreign-alloc :float :initial-element 1.0 :count 10))

(defparameter *dest* (foreign-alloc :float :count 10))

(memcpy *dest* *src* (* 10 (foreign-type-size :float))) ;; コピー

(dotimes (i 10)
  (format t "~a " (mem-aref *dest* :float i)))
;; => 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 

*fives* を要素数が5で値がすべて5.0の配列とする。これを *dest* の頭2つを飛ばしてコピーする。

(defparameter *fives* (foreign-alloc :float :initial-element 5.0 :count 5))

(memcpy (inc-pointer *dest* (* 2 (foreign-type-size :float))) 
        *fives* 
        (* 5 (foreign-type-size :float)))

(dotimes (i 10)
  (format t "~a " (mem-aref *dest* :float i)))
;; => 1.0 1.0 5.0 5.0 5.0 5.0 5.0 1.0 1.0 1.0 

foreign-type-size は C の sizeof にあたるもので構造体のサイズも返してくれる。 inc-pointer でポインタを進める。

記述が面倒なのでユーティリティを書いて

(defun mem-size (type n)
  (* n (foreign-type-size type)))

(defun mem-pos (pointer type &optional (n 0))
  (inc-pointer pointer (mem-size type n)))

こんな風に書けば少しは楽になる。

(memcpy (mem-pos *dest* :float 2) 
        *fives* 
        (mem-size :float 5))

解放も忘れずに

(foreign-free *src*)
(foreign-free *dest*)
(foreign-free *fives*)

良く使う関数は defcfun で定義しておいたほうが便利だが、たまにしか使わないなら foreign-funcall で呼んでもいい。ここでは sprintf を使ってみる。

(with-foreign-object (str :string)
  (foreign-funcall "sprintf" :string str :string "foobarbaz" :int) ;; sprintf の返り値は整数(文字数)
  (foreign-string-to-lisp str)) ;; C の文字列を Lisp の文字列に変換
;; =>
;; "foobarbaz"
;; 9

foreign-funcall の場合は返り値の型を一番最後に書く。 関数名 引数の型 引数 ... 返り値の型 の順序で並べる。