3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwigでC共有ライブラリのCFFIラッパー生成を試す

Last updated at Posted at 2020-02-12

SwigでC共有ライブラリのCFFIラッパー生成を試す

はじめに

cl-sdl2を使っていると、TEXTINPUT周りのイベントを取り扱おうとした際、Activeかどうかの判定などで使う関数がsdl2のネームスペースでアクセスできないような形になっていて使い辛いなと感じてます。

もしかするとリファレンスを見落としていたり使い方を間違っているのかもしれないんですが、

  • sdl2-ffi.functions.sdl-is-text-input-active
  • sdl2-ffi.functions:sdl-set-text-input-rect

のような関数は、

  • sdl2:is-text-input-active
  • sdl2:set-text-input-rect

のように書けたら良いのにと思ってます。
あと、ECLの場合cl-sdl2-ttfでsdl2をrequireしている箇所を削除しないとasdf:make-buildでバイナリにコンパイルして実行する際エラーが発生してしまうので、cl-sdl2-ttfもどうにかしたいです。Issueで修正箇所を送ってみたんですが、メンテナーの方はあまりアクティブに活動されてないようなのでしょんぼり……。

「そうだ、とりあえずCFFIを触ってみよう」

本格的に言語バインディングを作ろうとすると、それぞれの言語の作法に合わせた設計が必要になってくると思うんですが、ひとまずどういうものなのかやってみます。CFFIラッパージェネレーターを探すと、以下の3つが見つかりました。

このうち、verrazanoは開発中止でcl-autowrapを推してます。cl-autowrapがメジャーなようなんですが、ClangとLLVMが必須?のような説明で面倒だったので、BISONを使っている一番楽そうなSwigを選択しました。

環境

  • Debian GNU/Linux 10.2
  • ECL 16.1.3
  • Swig 3.0.12

Swigを使う

SwigはCFFIを使ってCで書かれた共有ライブラリを呼び出す際、ヘッダファイルから各種言語、環境向けのラッパーを生成してくれるツールです。メンテナー不在かサポートが不十分なため、CFFI向けの機能は最新版では一旦取り除かれていますが、Debianパッケージマネージャでインストールできる3系のバージョンであればCFFIが有効なため、それを使って生成してみます。

$ sudo apt install bison swig

テスト用Cプログラムの作成

以下のようなc言語のコードを用意します。2点間の距離を求めるdistance関数と、点を示すpointという構造体を定義しています。

mylib.h
#pragma once

typedef struct {
  double x;
  double y;
} point;

double distance(point* p1, point* p2);
mylib.c
#include <math.h>
#include "mylib.h"

double distance(point* p1, point* p2) {
  return sqrt(pow(p2->x - p1->x, 2) + pow(p2->y - p1->y, 2));
}

共有ライブラリとしてコンパイル

$ gcc -fpic -shared mylib.c -o mylib.so

Swigでラッパーを生成

Swig3.0のドキュメントCFFIサポートを確認し、lisp用のファイルを生成するための定義ファイルを記述します。

mylib.i
%module mylib

%include "mylib.h"

最初の%moduleは、この共有ライブラリラッパーのモジュール名を示し、%includeでヘッダーファイルを指定します。次のコマンドで、mylib.lispという名前でラッパーが生成されます。

$ swig -cffi -module mylib mylib.i
mylib.lisp
;; 〜Swig固有の出力は省略〜

(cffi:defcstruct point
	(x :double)
	(y :double))

(cffi:defcfun ("distance" distance) :double
  (p1 :pointer)
  (p2 :pointer))

mylib.lispを使うためのコードを書く

main.lisp
(pushnew (merge-pathnames #P"src/c/" *default-pathname-defaults*) cffi:*foreign-library-directories*)

(cffi:define-foreign-library mylib
    (t (:default "mylib")))
(cffi:load-foreign-library 'mylib)

(defun main()
  (cffi:with-foreign-objects ((p1 '(:struct point))
                              (p2 '(:struct point)))
    (cffi:with-foreign-slots ((x y) p1 (:struct point))
      (setf x 1.0 y 1.0)
      (format t "p1(~A,~A)~%" x y))
    (cffi:with-foreign-slots ((x y) p2 (:struct point))
      (setf x 2.0 y 2.0)
      (format t "p2(~A,~A)~%" x y))
    (format t "~A~%" (distance p1 p2))))

最初のpushnewは、CFFIに対して共有ライブラリの場所を追加しています。ここでは、src/cディレクトリ以下にmylib.soを置いているので、そのディレクトリを追加しました。

(cffi:define-foreign-library mylib
    (t (:default "mylib")))
(cffi:load-foreign-library 'mylib)

上記のコードは、mylib.soを読み込みmylibという名前で取り扱うことを定義し、ライブラリを読み込んでいます。本体はmain関数の部分で、p1p2というpoint構造体のポインタを作成し、それぞれのxとyに値をセット。最後にC言語側で定義したdistanceを呼び出しています。

実行結果は以下のようになります。

REPL
CL-USER> (load "mylib.lisp")
CL-USER> (load "main.lisp")
CL-USER> (main)
p1(1.0d0,1.0d0)
p2(2.0d0,2.0d0)
1.4142135623730951d0
NIL

うまく動いている感じなので、ひとまずこういうシンプルな形であればSwigのCFFI向けの機能は使えそうです。SDLのバインディングはものすごく大変そうなのでコピーして自分専用に修正したほうが早いのかもしれませんが、迷い中……。

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?