SWIGでハマる/関数ポインタ

SWIGを使う上でのキモが見えてきた。

SWIGを使えるようになる記事を書くために、基本的な調査やサンプルプログラムの作成を進めています。集中して取り組むことで今まで見えてなかった箇所が見えるようになり、なかなかに有意義な知見を得ることに成功しています。いつアウトプットするのよという疑問はさておき、その中でも現時点で一番厄介なことを記しておきます。

これまでの調査で、SWIGを使う上で(というより言語ラッパーを書く一般的な作業の)一番のキモは関数ポインタ/コールバック関数だとわかりました。C/C++における関数ポインタはまさにただのポインタです。32bitアーキテクチャのC/C++ならばわずか32bitの値に過ぎません。しかしPython(言語側)は違います。Pythonの関数(バイトコード)へのポインタと、そのPython関数を実行するC/C++で書かれたラッパー関数のポインタ、合わせて最低でも32x2=64bit以上の値が必要です。圧倒的にたりません。このギャップがSWIGを使って関数ポインタを扱う、ライブラリから言語側へコールバックを行うことを極端に難しくしています。が、解法がないわけではありません。

C++であれば関数ポインタ/コールバックはvirtual methodとそれを持つvirtual classで普通は実装しますから、言語用の橋渡しになる実装クラスを作ればよいでしょう。橋渡しクラスでは言語側の関数オブジェクトと、その他のバインドすべき値を保持し、C++からの呼び出し時にそれを変換するという動作になります。言語毎に橋渡しクラスを作らなきゃいけないという問題はありますが、これはそういうものだと思って諦めましょう。もしかしたら楽にしてくれる何かがあるかもしれませんが、現時点の私は知りません。

問題はCのほうで、C側のライブラリが完全に関数ポインタ一つしか受け取らない場合にはお手上げです。static変数に言語側の関数ポインタ等を保存し、それを駆動するドライバ関数を作り、ドライバ関数を固定で使うしかありません。こうしてしまうとstatic変数を使っている関係で再入不可(リエントラントではなくなる)になってしましますから、このドライバ関数とstatic変数の組を要求されるリエントラント数だけ用意し、切り替えるという使い方になるでしょう。SWIGでは用意されたコールバックの中から一つを選んで選択するということは可能です。

しかし一般的な設計のCライブラリならば、コールバック関数+コンテキストとなるパラメータ(だいたいはvoid*です)があります。この場合はC++と同じように橋渡しの関数とデータを工夫すれば良いでしょう。

といった感じでSWIGにおける関数ポインタの取り扱いについてざっと俯瞰してみました。さあ、あとは実装するだけです。