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

# 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つが見つかりました。

- [cl-autowrap & c2ffi](https://github.com/rpav/cl-autowrap)
- [Swig](http://swig.org)
- [verrazano](https://common-lisp.net/project/fetter/)

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

## 環境

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

## Swigを使う

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

```bash
$ sudo apt install bison swig
```

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

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

```c:mylib.h
#pragma once

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

double distance(point* p1, point* p2);
```

```c: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));
}

```

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

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

## Swigでラッパーを生成

[Swig3.0のドキュメント](http://swig.org/Doc3.0/Sections.html#Sections)の[CFFIサポート](http://swig.org/Doc3.0/Lisp.html#Lisp_nn3)を確認し、lisp用のファイルを生成するための定義ファイルを記述します。

```mylib.i
%module mylib

%include "mylib.h"
```

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

```bash
$ swig -cffi -module mylib mylib.i
```

```common-lisp:mylib.lisp
;; 〜Swig固有の出力は省略〜

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

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

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

```common-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関数の部分で、`p1`、`p2`というpoint構造体のポインタを作成し、それぞれのxとyに値をセット。最後にC言語側で定義したdistanceを呼び出しています。

実行結果は以下のようになります。
```common-lisp: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のバインディングはものすごく大変そうなのでコピーして自分専用に修正したほうが早いのかもしれませんが、迷い中……。


## 参考

- [Swig3.0 Documentation](http://swig.org/Doc3.0/Sections.html#Sections)
- [CFFI User Manual](https://common-lisp.net/project/cffi/manual/cffi-manual.html)
- [CFFI:Common LispからCの機能を利用する](https://qiita.com/masatoi0/items/6b8017902fe067679e01)