PythonでCライブラリを扱うのにctypesはよく使います。
が、このライブラリで構造体配列を持つ構造体を扱う方法が実はあまりサンプルがなかったので、備忘録を兼ねて記事にします。
Cライブラリ
struct x{
int x;
int res_calc;
};
struct x_list{
struct x xs[100];
};
int calc_x(struct x_list *x_list);
#include <stdio.h>
#include "sample.h"
int calc_x(struct x_list *x_list){
int i = 0;
for(i=0; i<100; i++){
(x_list)->xs[i].res_calc = (x_list)->xs[i].x + 10;
}
return 0;
};
struct x_listはstruct x * 100の配列を持つ構造体です。
inc calc_xはx_listのポインタを受け取り、struct x.xに+10をしてその結果をstruct x.res_calcに格納します。
一応ビルド
この記事を試してみたい方向けにサンプルソースのビルドも書いておきます。
gcc -fPIC -shared -o sample.so sample.c
Pythonの呼び出し側
まず素直にctypesのリファレンスにあるような書き方をしてみます。
import ctypes
class x(ctypes.Structure):
_fields_ = [('x', ctypes.c_int),
('res_calc', ctypes.c_int)]
class x_list(ctypes.Structure):
_fields_ = [('xs', x * 100)]
handle = ctypes.cdll.LoadLibrary('sample.so')
handle.calc_x.argtypes = (ctypes.POINTER(x_list),)
handle.calc_x.restypes = ctypes.c_int
_list = [x(i, 0) for i in range(100)]
_x_list = x_list(*_list)
rt = handle.calc_x(ctypes.byref(_x_list))
for i in range(100):
print('{}'.format(_x_list.xs[i].res_calc))
struct x_list.xsは、struct x[100]なので、x * 100とします。
class xを100個納めたリストを作成し、x_listの生成時にアンパックして与えます。
そうすればx_list.xsにはxが100個与えられるので、うまくいくように思えます。
が、実際には以下のエラーになります。
Traceback (most recent call last):
File "/Users/****/do_sample.py", line 15, in <module>
_x_list = x_list(*_list)
TypeError: incompatible types, x instance instead of x_Array_100 instance
型があってない、そこはx_Array_100を入れろというエラーです。
じゃあどうするかというと、x_Array_100を明示的に定義してやるとうまくいきます。
import ctypes
class x(ctypes.Structure):
_fields_ = [('x', ctypes.c_int),
('res_calc', ctypes.c_int)]
class x_list(ctypes.Structure):
x_Array_100 = x * 100
_fields_ = [('xs', x_Array_100)]
handle = ctypes.cdll.LoadLibrary('sample.so')
handle.calc_x.argtypes = (ctypes.POINTER(x_list),)
handle.calc_x.restypes = ctypes.c_int
_list = [x(i, 0) for i in range(100)]
_x_list = x_list(x_list.x_Array_100(*_list))
rt = handle.calc_x(ctypes.byref(_x_list))
for i in range(100):
print('{}'.format(_x_list.xs[i].res_calc))
結果は省略しますが、これだと動きます。
x_Array_100は、class x_listの中でなくてもいいですが、ここに入れた方が収まりがいい気がするので、私はこうしてます。
pythonの公式ライブラリのリファレンス見ても、構造体の入れ子や配列の作り方は書いてあるのですが、構造体配列を持つ構造体のclass定義の書き方とか、そのclassのインスタンスの生成の書き方は書かれてないんですよね。
ざっとググってみても、構造体配列の定義の仕方を書いてある記事はあっても、それを実際にインスタンス生成したり、関数に引数として与えたりの記事は見当たりませんでした。
実践では全くないケースでもない(というか私はぶち当たった)と思うので、この記事が参考になれば幸いです。