nptでsoファイルを読み込む
前回nptでDLLファイルを読み込む - nptclのブログの続きです。
前の投稿ではnptにDLLファイルを読み込ませましたが、
soファイルもやりたいな、ということでやりました。
個人的にはsoファイルの作成は初めてなので、 いろいろと勉強になりました。
1. nptのコンパイルから
nptのコンパイルの方法が変わります。
次のオプションをつけてください。
- nptコンパイル時
-DLISP_DYNAMIC_LINK
リンク時は-ldl
が必要になるかと思います。
コンパイルスクリプトは修正して普通に利用できるようにしています。
利用可能かどうかは次のようにして調べます。
$ npt --version-script | grep dynamic-link dynamic-link true
2. soファイルの作成
それではsoファイルを作りましょう。
やり方はDLLと同じですが、詳しく書いていきます。
下記のファイルを用意してください。
npt/develop/dlfile/
lispdl.c
lispdl.h
ひな形を示します。
#include "lispdl.h" int lisp_dlfile_main(lisp_dlfile_array ptr) { return lisp_dlfile_update(ptr); }
もし初期化と解放処理が必要な場合は、次のようにするそうです。
#include "lispdl.h" void init(void) __attribute__((constructor)); void fini(void) __attribute__((destructor)); void init(void) { /* 初期化 */ } void fini(void) { /* 解放 */ } int lisp_dlfile_main(lisp_dlfile_array ptr) { return lisp_dlfile_update(ptr); }
lisp_dlfile_main
関数は、npt
が真っ先に呼び出す初期化用の関数です。
この関数でやることは、lisp_dlfile_update
関数を呼び出すだけです。
lisp_dlfile_update
関数は、呼び出し元のnpt
環境から色んな機能をso内部に持ってくる命令です。
npt
はsoファイルを読み込むと
まずはlisp_dlfile_main
関数を呼び出そうとします。
#include
しているlispdl.h
は、
amalgamationが提供するlisp.h
のsoバージョンなので、
nptの説明書に記載されている方法で開発できます。
単に"Hello"
という文字列を返却する関数hello_
を作ってみます。
公開する関数は、必ず脱出関数にしてください。
#include "lispdl.h" int lisp_dlfile_main(lisp_dlfile_array ptr) { return lisp_dlfile_update(ptr); } int hello_(addr rest) { addr control, x; lisp_push_control(&control); x = Lisp_hold(); lisp_string8_(x, "Hello"); lisp_set_result_control(x); return lisp_pop_control_(control); }
終わりです。
ではコンパイルをしましょう。
上記のファイルがaaa.c
であるとします。
$ cc -fPIC -shared -o aaa.so lispdl.c aaa.c
いろいろと知らない引数が出てきました。
-fPIC
は再配置可能なコードを出すんだそうです。
-fpic
もあるらしいですが、上記のように大文字にしておけば安全です。
-shared
は、soファイル出力用だそうです。
lispdl.c
は忘れないでくださいね。
忘れると、後でなんでdlfile
で失敗するんだって延々と悩むことになります。
成功すれば、aaa.so
ファイルが出力されます。
3. nptで実行
nptを起動してください。
同じディレクトリにaaa.so
をコピーしてください。
まずは操作が面倒なのでnpt-system
パッケージをuse
します。
* (use-package 'npt)
soを読み込むには、dlfile
関数を使用します。
dlfile
の第一引数にopen
を指定します。
* (setq x (dlfile 'open "./aaa.so")) #<PAPER 0 ...>
Windows版とは違い、"aaa.so"
ではなく、
"./aaa.so"
のようにしないと失敗しました。
そういえば大昔はカレントディレクトリ.
がPATH
に入ってたの知ってますか?
セキュリティホールになったので廃止されたんです。
それを思い出してしまいました。
あとはWindows版と同じです。
* (setq y (dlfile 'call x "hello_")) #<PAPER 0 ...> * (dlcall y) "Hello"
soの開放は次の通り。
* (dlfile 'close x)
4. その他の機能
DLLとsoのどちらも共通した話のなのですが、
openして放置したらそれはリークです。
例えばこんな感じ。
* (dlfile 'open "./aaa.so") #<PAPER 0 #x80174ee28> * (dlfile 'open "./aaa.so") #<PAPER 0 #x80174f8d8> * (dlfile 'open "./aaa.so") #<PAPER 0 #x801750388> * (dlfile 'open "./aaa.so") #<PAPER 0 #x801750e38> *
こうなると、プロセスを終了させない限り
aaa.so
の残骸がメモリに残り続けるわけです。
それはちょっとどうかと思ったので、一応救済措置を設けました。
dlfile
にlist
を指定して下さい。
* (dlfile 'list) (#<PAPER 0 #x801750e38> #<PAPER 0 #x801750388> #<PAPER 0 #x80174f8d8> #<PAPER 0 #x80174ee28>)
こんな感じで、closeされていない、open中のdlfile
の一覧が得られます。
もし(dlfile :close x)
でcloseされた場合は、この一覧に出てきません。
リークに困っているなら、このリストにあるオブジェクトをcloseしていけばいいわけです。
それにしたって情報がなさすぎなので、
せめて何のファイルをopen
したのかくらいの情報を得る命令を追加しました。
dlfile
にinfo
指定すると、引数の情報を出力します。
例えばこんな感じ。
* (setq x (dlfile 'open "./aaa.so")) #<PAPER 0 #x801752dd0> * (dlfile 'info x) #P"./aaa.so" T
第一返却値は、dlfile
オブジェクトの場合はパス名を返却します。
第二返却値は、closeされていなかったらT
を返却します。
何も考えずに全部閉じたい場合は次のようにしてください。
* (mapcar (lambda (x) (dlfile 'close x)) (dlfile 'list)) (T T T T T)
全部閉じた場合はこんな感じになります。
* (dlfile 'list) NIL