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

cffi を学ぶ(1) 変数と配列

lisp

cffi を学ぶ(1) 変数と配列 - TIPS
cffi を学ぶ(2) 配列の続きと関数 - TIPS
cffi を学ぶ(3) Cのライブラリとコールバック - TIPS
cffi を学ぶ(4) C のライブラリを使う - TIPS
cffi を学ぶ(5) 構造体 - TIPS
cffi を学ぶ(6) 構造体の続きと translate-*-foreign - TIPS

とりあえずの目標は cl-opengl などのパッケージを使わずに cffi で OpenGL を動かすところまで。

参考
レファレンス 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)

変数について:

(defcvar "errno" :int) ;; => *ERRNO* (LISP 名は *ERRNO* になる)
(foreign-funcall "strerror" :int *errno* :string) ;; => "No such file or directory"
(setf *errno* 5)
(foreign-funcall "strerror" :int *errno* :string) ;; => "Input/output error"

defcvar で定義した変数は C 側にすでに定義されているグローバル変数には触れるが、次の例はまずい。

(defcvar "hogehoge" :float) ;; これは通る(定義はいつでも可能)
(setf *hogehoge* 1.0) ;; これはエラー(C 側に存在しないから)

もし hogehoge が自分で作った Cライブラリのグローバル変数であれば問題ない。

ポインタについて:
配列は動的に確保する。Lisp 側から確保できるし代入などの操作もできる。

(defparameter *x* (foreign-alloc :float :count 10)) ;; 要素数10の配列を確保

;; 代入
(dotimes (i 10)
  (setf (mem-aref *x* :float i) (* i 2.0)))

(dotimes (i 10)
  (format t "~a " (mem-aref *src* :float i)))
;; => 0.0 2.0 4.0 6.0 8.0 10.0 12.0 14.0 16.0 18.0 

(foreign-free *x*) ;; メモリ解放

foreign-alloc でメモリを確保するときに初期値を代入してしまうこともできる。

;; 全要素を 0.0 で初期化 :initial-element
(defparameter *x* (foreign-alloc :float :initial-element 0.0 :count 10)) 

;; 要素数2の配列を確保して 1.0,2.0 を代入 :initial-contents
(defparameter *x* (foreign-alloc :float :initial-contents '(1.0 2.0) :count 2)) 

C の malloc, free を defcfun で定義して使うこともできるがそれは上記サイトを参照。

メモリ確保と解放をセットで行うためのマクロもある。

(with-foreign-object (array :int 10)
  (loop for i below 10
     do (setf (mem-aref array :int i) (random 100)))
  (loop for i below 10 collect (mem-aref array :int i)))
  ;; => ランダムなリストが返る

要素数50万の配列でテストしたところ setf & mem-aref で代入するのは C の1.5倍から2.0倍くらいの時間がかかる(sbcl)。

二次元の配列も作ることができる。

(with-foreign-objects ((array :pointer 2) (row1 :int 2) (row2 :int 2))
  (setf (mem-aref array :pointer 0) row1
        (mem-aref array :pointer 1) row2)
  (dotimes (i 2)
    (dotimes (j 2)
      (setf (mem-aref (mem-aref array :pointer i) :int j)  (* (1+ i) (1+ j)))))
  (dotimes (i 2)
    (dotimes (j 2)
      (format t "~d " (mem-aref (mem-aref array :pointer i) :int j)))
      (format t "~%")))
;; => 
;; 1 2 
;; 2 4 

多次元配列になると作るのも面倒だしアクセスするのも mem-aref を何度も呼ぶ必要があるので1次元配列をやりくりしたほうがいいかも知れない。