2015年7月8日水曜日

MinGW gcc とctypesモジュールを用いてpythonからc関数を呼び出してみる 2

前に構造体を渡すような関数までやったけど、構造体の配列を渡すようなこともよくありそう。簡単な例で試してみたのでメモしておきます。
呼び出されるc関数は、以下の通り。

#include <stdio.h>

typedef struct {
 char name[20];
 int age;
} NAMEandAGE;

int sum_ages(int sz, NAMEandAGE *na)
{
 int i;
 int sum = 0;
 
 for (i=0; i<sz; i++){
  printf("%s %d\n", na[i].name, na[i].age);
  sum = sum + na[i].age;
 }
 return sum;
}
文字列と整数のフィールドをもつ、構造体NAMEandAGEの配列を渡して、 要素の値を表示し、ageの和を返す関数です。
配列は、pythonスクリプトで作成するため、その長さが分からないので、 整数szで長さを渡しています。

ソースファイルをstructures.cというファイルに保存して、

gcc -Wall -c structures.c

でコンパイルします。structures.oというオブジェクトファイルができますので、

gcc -shared -o structures.so structures.o

として、structures.soという、共有ライブラリファイルを作成します。
これを呼び出す、pythonスクリプトは、以下の通り。

import ctypes

class NAMEandAGE(ctypes.Structure):
    _fields_ = [('name', ctypes.c_char*20),
                ('age', ctypes.c_int)]

if __name__ == '__main__':
    mydll = ctypes.CDLL("structures.so")

    data = [('name1', 1), ('name2', 2)] 
    nameandages = (NAMEandAGE * len(data))()
    for n, d in zip(nameandages, data):
        n.name = d[0]
        n.age = d[1]

    result = mydll.sum_ages(ctypes.c_int(len(data)), ctypes.pointer(nameandages))
    print result

配列は、(構造体のクラス * 要素数)で構造体配列のクラスを作って、それを用いてインスタンスを生成するようです。 その後、リストdataの値で、各要素のフィールドを設定しました。
関数を呼び出すときは、ctypes.pointer()を使って、構造体配列のポインタを渡せばよいようです。