3
2

More than 1 year has passed since last update.

ctypesで構造体配列を持つ構造体を扱う

Last updated at Posted at 2022-11-09

PythonでCライブラリを扱うのにctypesはよく使います。
が、このライブラリで構造体配列を持つ構造体を扱う方法が実はあまりサンプルがなかったので、備忘録を兼ねて記事にします。

Cライブラリ

sample.h
struct x{
    int x;
    int res_calc;
};

struct x_list{
    struct x xs[100];
};

int calc_x(struct x_list *x_list);
sample.c
#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のリファレンスにあるような書き方をしてみます。

do_sample.py
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のインスタンスの生成の書き方は書かれてないんですよね。

ざっとググってみても、構造体配列の定義の仕方を書いてある記事はあっても、それを実際にインスタンス生成したり、関数に引数として与えたりの記事は見当たりませんでした。

実践では全くないケースでもない(というか私はぶち当たった)と思うので、この記事が参考になれば幸いです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2