C/C++のライブラリをPythonから呼び出す方法として、SWIGを使った方法をとっているものがありました。このライブラリを呼び出す際に、引数としてvoid*をとる関数の場合にうまくPythonのデータを渡せないことがありました。今回はこのような場合の回避策を考えてみました。
なお、以下のサンプルは、
Python 2.6.2
SWIG 1.3.36
Ubuntu 9.04 デスクトップ 日本語 Remix
で試しました。
問題の再現
今回問題となったケースと同じような振る舞いをするサンプルコードを示します。まずは、C/C++ライブラリに該当するコードです。
test_api.h
#include <iostream> int test_char(char * src); int test_void(void * src);
test_api.cpp
#include <cstring> #include "test_api.h" int test_char(char * src) { char * ptr = src; std::cout << "size is " << strlen(ptr) << "\n"; for (int i = 0; i < strlen(ptr); i++) { std::cout << *(ptr+i) << "\n"; } return 0; } int test_void(void * src) { char * ptr = reinterpret_cast<char *>(src); std::cout << "size is " << strlen(ptr) << "\n"; for (int i = 0; i < strlen(ptr); i++) { std::cout << *(ptr+i) << "\n"; } return 0; }
上記の関数をライブラリとみなしたときに、Pythonバインディングを作成するためのSWIGのインターフェースファイルは次のようにしました。
test_api.i
%module test_api %{ #include "test_api.h" %} int test_char(char *); int test_void(void *);
これをSWIGを使い、次のようにpythonモジュールを作成します。
swig -c++ -python test_api.i gcc -c test_api.cpp gcc -I/usr/include/python2.6 -c test_api_wrap.cxx gcc -fPIC -shared test_api.o test_api_wrap.o -lm -lstdc++ -o _test_api.so
これをpythonで呼び出そうとすると、
のように、void*をとる引数を持つ関数の呼び出しに失敗することがわかります。
なお、上記の例で分かるように今回の場合はライブラリに渡したいデータを、Python側では文字列の形で持っていることを想定しています(これは別のライブラリからデータを取得しているため、このようにしています)。
解決方法
例外のメッセージをみると分かりますが、どうも型チェックでひっかかっているようです。 ということは、型チェックをうまく回避してやれば呼び出しに成功するだろうと思われます。
今回はPythonバインディングモジュールを変更したくなかったので、ポインタを変換するだけの関数からなる補助モジュールを追加で作成しました。
もし、SWIGのインターフェースファイル(~.i)を編集して、C/C++ライブラリとPythonのバインディングを行うモジュールを再作成できるのであれば、ここに示した方法以外にもいろいろな方法があると思います。ネット等で調べて見てください。
test_cnv.h
#include <iostream> void * convert(char * src);
test_cnv.cpp
#include "test_cnv.h" void * convert(char * src) { return reinterpret_cast<void *>(src); }
test_cnv.i
%{ #include "test_cnv.h" %} void * convert(char *);
これを先ほどと同じようにモジュールを作成します。
swig -c++ -python test_cnv.i gcc -c test_cnv.cpp gcc -I/usr/include/python2.6 -c test_cnv_wrap.cxx gcc -fPIC -shared test_cnv.o test_cnv_wrap.o -lm -lstdc++ -o _test_cnv.so
Pythonからの利用時には、 先ほどのtest_apiモジュールと今回追加で作成したtest_cnvモジュールの両方を読み込んでやります。すると次の画像に示すように、
となり、問題なくライブラリの関数が呼べることが分かりました。