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

cffi を学ぶ(6) 構造体の続きと translate-*-foreign

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

構造体の続きで C の複素数を使ってみる。 C の複素数

struct {double c[2]};

の形のようです。

まず C のライブラリを書く。今回は complex.h をインクルードする。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>

double complex ret_complex()
{
    double complex z = 1.0 + 2.0*I;
    return z;
}

double complex pow2(double complex z)
{
    return z * z;
}

これをコンパイル

gcc -shared libcomplex.c -o libcomplex.so

repl で以下を評価する。

(load "~/quicklisp/setup.lisp")
(ql:quickload "cffi")
;; 構造体の値渡しには cffi-libffi をロードする
(ql:quickload "cffi-libffi") 

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

(in-package :scratch)

(define-foreign-library libcomplex
  (:darwin "libcomplex.so"))

(use-foreign-library libcomplex)

;; :class は translate-*-foreign でメソッドを定義するために必要
(defcstruct (DOUBLE-COMPLEX :class DOUBLE-COMPLEX-TYPE)
  (c :double :count 2))

;; ライブラリの関数を定義する。返り値は DOUBLE-COMPLEX 構造体
(defcfun "ret_complex" (:struct DOUBLE-COMPLEX))

(defcfun "pow2" (:struct DOUBLE-COMPLEX)
  (z (:struct DOUBLE-COMPLEX)))

;; 構造体は plist として返る
(ret-complex)
;; => (C #.(SB-SYS:INT-SAP #X01BFFEE8))

;; したがって getf で取り出せる
(getf (ret-complex) 'c)
;; => #.(SB-SYS:INT-SAP #X01BFFEE8)

;; lisp の complex にする
(let ((z (getf (ret-complex) 'c)))
  (complex (mem-aref z :double 0)
           (mem-aref z :double 1)))
;; => #C(1.0d0 2.0d0)

;; translate-from-foreign を DOUBLE-COMPLEX-TYPE に対して定義しておく
(defmethod translate-from-foreign (z (type DOUBLE-COMPLEX-TYPE))
  (with-foreign-slots ((c) z (:struct DOUBLE-COMPLEX))
    (complex (mem-aref c :double 0)
             (mem-aref c :double 1))))

;; そうすれば 単に (ret-complex) を実行すれば lisp の complex が返る
(ret-complex)
;; => #C(1.0d0 2.0d0)

;; C の関数の引数に lisp の complex を渡す
(defmethod translate-into-foreign-memory (z (type DOUBLE-COMPLEX-TYPE) ptr)
  (with-foreign-slots ((c) ptr (:struct DOUBLE-COMPLEX))
    (setf (mem-aref c :double 0) (realpart z)
          (mem-aref c :double 1) (imagpart z))))

(pow2 #c(0.0d0 1.0d0))
;; => #C(-1.0d0 0.0d0)

これで C の関数に lisp複素数を渡しても何の違和感もなくなる。

ところで最初は以下でうまくいくと思った。

(defmethod translate-to-foreign (z (type DOUBLE-COMPLEX-TYPE))
  (let ((ptr (foreign-alloc '(:struct DOUBLE-COMPLEX))))
    (with-foreign-slots ((c) ptr (:struct DOUBLE-COMPLEX))
      (setf (mem-aref c :double 0) (realpart z)
            (mem-aref c :double 1) (imagpart z)))
    (values ptr t)))

(defmethod free-translated-object (ptr (type DOUBLE-COMPLEX-TYPE) param)
  ;; 引数の ptr, param は translate-to-foreign の返り値
  (when param (foreign-free ptr)))

これで pow2 を呼ぶと translate-into-foreign-memory に関するエラーがでる。どうも内部でこの関数を呼んでいるらしい。そこでこの2つの後に上の translate-into-foreign-memory メソッド定義を入れるとうまくいった。逆にいえば今回のケースではこの2つは不要ということらしい。