ここでは,libuvのチュートリアル本,uvbookの第2.2節に載っている以下のHello WorldコードをLispで実装する手順を解説します.まず始めにCFFIを使って直接libuvの関数を呼び出す方法を解説し,最後にlibuvラッパーのcl-libuvを使った実装方法を解説します.間違い,コメント等ありましたら,@waterloo_jpまでお願いします.
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
int main() {
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
printf("Now quitting.\n");
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop);
free(loop);
return 0;
}
ライブラリのロード
Cで書かれたlibuv
の関数を呼び出す前に,libuv
ライブラリをロードする必要があります.libuv
がシステムにインストールされている場合,cffi:load-foreign-library
関数を使ってロードすることができます.ファイル名を直接指定するよりも,拡張子を省略して:default
指定すると,環境に応じてライブラリがロードされます.
(require 'cffi)
(cffi:load-foreign-library '(:default "libuv"))
OSに応じてロードするライブラリを細かく指定したい場合は,cffi:define-foreign-library
関数でライブラリを定義した後,cffi:use-foreign-library
を使ってロードします.cffi:use-foreign-library
で複数回ロードするとエラーが出るので下の例のようにロード済みかどうか確認します.
(cffi:define-foreign-library libuv
(:darwin (:or "libuv.1.dylib" "libuv.dylib"))
(:unix (:or "libuv.so.1" "libuv.so"))
(:windows (:or "libuv.dll" "libuv-1.dll"))
(t (:default "libuv")))
(unless (cffi:foreign-library-loaded-p 'libuv)
(cffi:use-foreign-library libuv))
uv_loop_t
構造体変数の準備
Cのサンプルコード6行目にある以下のコードを実装します.
まず始めに,size_t
型をCFFIで使えるように設定し,次にメモリの割当方法を学び,最後にポインタ変数を作成します.
型の定義方法
libuvのAPIドキュメントにはuv_loop_t
構造体の詳細は示されていませんが,代わりに同じページの下の方に,uv_loop_t
の大きさをsize_t
で返すuv_loop_size(void)
関数があります.Cの組込み型は通常CFFIから利用可能ですが,size_t
は何故か利用できないので,まずsize_t
と同じ大きさのunsigned int
型をエイリアスとしてCFFI版teypdefしておきましょう.
(cffi:defctype size_t :uint)
C関数のコール方法
Cの関数をcallするには2通りの方法があります.CFFIを使って直接関数を起動する方法と,CFFIでCの関数にLispシンボルをバインドしてLisp関数として起動する方法です.
1つ目の直接起動する方法はcffi:foreign-funcall
を使う方法です.下の例の様にcffi:foreign-funcall
に関数名,戻り値型,(引数型 引数値)から成る引数リストを指定して呼びます.
(cffi:foreign-funcall "uv_loop_size" size_t)
2つ目のLispシンボルにバインドして呼ぶ方法はcffi:defcfun
マクロを使います.,cffi:defcfun
マクロを使って以下のように定義すると,アンダースコアがハイフンに変わったLisp関数が定義されます.
(cffi:defcfun "uv_loop_size" size_t) ; uv-loop-sizeにバインド
(uv-loop-size) ; Lisp関数として起動
uv_loop_size
は何回も呼ばないので,ここではcffi:foreign-funcall
を使った1番目の方法を採用することにします.
メモリの割当て方法
Lispからメモリを割り当てる場合,型に合わせたメモリ割り当てと,バイトサイズを指定したメモリ割り当ての2通りの方法があります.型を指定してその大きさのメモリを割り当てる場合には,cffi:foreign-alloc
かwith-foreign-objects
を使います.バイトサイズを指定してメモリを割り当てるには,with-foreign-pointer
を使います.
CFFIでメモリを割り当てる際にはバイト(8bit)単位で指定します.そこで,size_t
型のバイト数を取得する必要があります.CFFIから利用可能なCの組込み型は,例えば(cffi:foreign-type-size :int)
でバイト数を取得できますが,先ほど作成したエイリアスsize_t
を使う場合にはクォートが必要です.後でコードが読みやすくなるように,ここではuv_loop_t
型のバイト数を*size*
に格納しておきましょう.
(* (cffi:foreign-type-size 'size_t)
(cffi:foreign-funcall "uv_loop_size" size_t))
バイト数を指定してメモリを割り当てるにはcffi:with-foreign-pointer
マクロを使います.以下のコードで本体処理部分でのみ*size*
バイトのメモリがuvloop
にバインドされます.メモリは通常スタックに割り当てられwith
ブロックを抜けると解放されます.
(cffi:with-foreign-pointer (uvloop *size*)
'処理本体)
バイト数を指定する代わりに型を指定してメモリを割り当てるcffi:with-foreign-object
もあります.型の後ろに整数を指定すると,与えられた型が連続した領域を確保することができます.
(cffi:with-foreign-object
(uvloop 'size_t (cffi:foreign-funcall "uv_loop_size" size_t))
'処理本体)
malloc()
の様にヒープにメモリを割り当てたい時にはcffi:foreign-alloc
を使います.この関数はバイト数ではなく型をとるので,任意の大きさを得るには:count
キーワードで連続した領域数を指定します.この方法でメモリを割り当てた場合,cffi:foreign-free
で解放する必要があります.
;; メモリを割当てポインタを*uvloop*にバインド
(defparameter *uvloop*
(cffi:foreign-alloc 'size_t
:count (cffi:foreign-funcall "uv_loop_size" size_t)))
;; メモリ解放
(cffi:foreign-free *uvloop*)
ここでは,1番始めに紹介した,cffi:with-foreign-pointer
を使って実装することにします.
イベントループの初期化
ここまででuv_loop_t
型へのポインタ変数を用意できました.次はサンプルCプログラムの7行目の以下のコードを実装します.
uv_loop_init
関数の定義は,int uv_loop_init(uv_loop_t* loop)
です.引数がポインタの場合,CFFIでの型指定は:pointer
を使います.Lispシンボルとバインドして呼び出したいなら,
(cffi:defcfun "uv_loop_init" :int (loop :pointer))
で,ポインタを引数loop
に取り,int
を返すuv-loop-init
関数が定義できます.呼び出し方法はハイフンに注意して(uv-loop-init uvloop)
です(uvloop
は前節でメモリ割当てしたポインタ変数).
直接呼ぶ場合には,引数と戻り値の記述順序がcffi:defcfun
と逆で,
(cffi:foreign-funcall "uv_loop_init" :pointer uvloop :int)
で呼出せます.先ほどのメモリ割当てコードと合わせるとここまでの処理は以下の通りです.
;; install libuv and cffi first.
(require 'cffi)
(cffi:load-foreign-library '(:default "libuv"))
(cffi:defctype size_t :uint)
(defparameter *size* (* (cffi:foreign-type-size :uint)
(cffi:foreign-funcall "uv_loop_size" size_t)))
(cffi:with-foreign-pointer (uvloop *size*)
(cffi:foreign-funcall "uv_loop_init" :pointer uvloop :int))
ポインタ変数について
上の例ではポインタを取る関数にuvloop
を渡しました.この例では出て来ませんが,ポインタが指し示す先を得る方法も説明しておきましょう.ポインタの参照先を得るにはcffi:mem-ref
を使います.例えば,char
型のポインタ変数ptr
の参照先は以下で得られます.
(cffi:mem-ref ptr :char)
ptr
がchar
配列の場合,ptr[3]
は(cffi:mem-ref ptr :char 3)
でアクセスします.参照先に値を設定する場合は以下のように書きます.
(setf (cffi:mem-ref ptr :char 3) (char-code #\a))
CFFIによる実装仕上げ
残りのCコードは以下の通りです.
最後のメモリ解放はcffi:with-foreign-pointer
を使った場合,自動的に行われるので必要ありません.
uv_run()
関数のシグニチャはint uv_run(uv_loop_t* loop, uv_run_mode mode)
で,uv_run_mode
型はここで以下の様に定義されているenum
型です.
typedef enum {
UV_RUN_DEFAULT = 0,
UV_RUN_ONCE,
UV_RUN_NOWAIT
} uv_run_mode;
Cのuv_run
を起動するときに引数でUV_RUN_DEFAULT
の値を渡さなければいけないので,CFFIからCのenum型を定義し用意します.
(cffi:defcenum :uv-run-mode
(:uv-run-default 0)
:uv-run-once
:uv-run-nowait)
uv_loop_close(loop)
はint
を返す関数です.以上をまとめるとHello WorldサンプルコードのLisp版は以下のようになります.
;; install libuv and cffi first.
(require 'cffi)
(cffi:load-foreign-library '(:default "libuv"))
(cffi:defctype size_t :uint)
(defparameter *size* (* (cffi:foreign-type-size :uint)
(cffi:foreign-funcall "uv_loop_size" size_t)))
(cffi:defcenum :uv-run-mode
(:uv-run-default 0)
:uv-run-once
:uv-run-nowait)
(cffi:with-foreign-pointer (uvloop *size*)
(cffi:foreign-funcall "uv_loop_init" :pointer uvloop :int)
(format t "Now quitting.~%")
(cffi:foreign-funcall "uv_run" :pointer uvloop :uv-run-mode :uv-run-default)
(cffi:foreign-funcall "uv_loop_close" :pointer uvloop :int))
cl-libuvを使った実装
CFFIの実装を理解していれば基本的には同じです.cl-libuvではアンダースコアの関数名をハイフンに変えているだけです.
CFFIを用いた前節までの実装では,malloc(sizeof(uv_loop_t))
を実装しました.libuvには他言語からの利用を考慮してuv_loop_t
インスタンスを初期化してポインタを返すuv_default_loop()
関数が用意されています.ここではこの関数を用いてメモリアロケーションを回避しています.
uv_run_mode
型はcl-libuv/bindings.lisp
ファイルでCのenum型として定義されています.Cのenum変数にアクセスするには,cffi:foreign-enum-value
に型名を指定してアクセスします.cl-libuvでは,lispify
マクロ(cl-libuv/wrapper.lisp
で定義)でenum変数名の先頭からuv-
を取り除き:keyword
パッケージにinternするので,UV_RUN_DEFAULT
は:RUN_DEFAULT
でアクセスできます.
(require 'cl-libuv)
(defparameter *loop* (uv:uv-default-loop))
(uv:uv-run *loop* (cffi:foreign-enum-value
'uv:uv-run-mode :run-default))
(format t "Now quitting.~%")
(uv:uv-loop-close *loop*)