Table of Contents
モチベーション
共有ライブラリをPythonから呼びたい
Foreign Function Interface (FFI)
一般に,あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するためには,Foreign Function Interface (FFI)を用意する必要がある.
PythonからC/C++の共有ライブラリを呼ぶ
PythonのFFIとして, ctypes や Python.h , Boost.Python などがある.
ctypesはPythonに標準で組み込まれているので,import
するだけで使える.
ctypesでC/C++の共有ライブラリを呼ぶ方法
共有ライブラリ内の関数の入出力に関して,以下のパターンを想定し,それぞれについてctypesから呼び出す方法を実装した.
- 引数が無い関数を呼ぶ例
- 引数が複数ある関数を呼ぶ例
- 関数の引数に参照渡しする例
- 関数の引数に構造体を値渡しする例
- 関数の引数に構造体を参照渡しする例
- 入れ子の構造体を参照渡しする例
- 関数ポインタを渡す例
以下は具体的な実装であり,how-to-use-ffi-in-pythonにもpushした.
共有ライブラリ内の関数群
以下のようなCのソースコードを例えば myutils.c
とし, gcc -shared -o libmyutils.so myutils.c
などで共有ライブラリを作成したとする.
#include <stdio.h>
// 1. helloworld
int helloworld(void){
printf("Hello World !\n");
return 0;
}
// 2. pass two values
int sum(int a, int b){
return a+b;
}
// 3. pass a pointer value
int pass_value_by_reference(int *value){
int tmp = *value;
*value = 321;
printf("%d -> %d\n", tmp, *value);
return 0;
}
// 4. pass a struct by value
typedef struct {
int value;
char str[100];
}pass_struct_by_value_t;
int pass_struct_by_value(pass_struct_by_value_t my_struct){
printf("%d %s\n", my_struct.value, my_struct.str);
return 0;
}
// 5. pass a pointer struct
typedef struct {
int value;
char str[100];
int result;
}pass_struct_by_reference_t;
int pass_struct_by_reference(pass_struct_by_reference_t *my_struct_p){
printf("%d %s\n", my_struct_p->value, my_struct_p->str);
my_struct_p->result = 123;
return 0;
}
// 6. pass a nested struct
typedef struct {
char ip_address[64];
}ip_t;
typedef struct {
char name[128];
int ip_num;
ip_t *ip;
}machine_t;
typedef struct {
int machine_num;
machine_t *machine;
}servers_t;
int show_servers(servers_t *servers){
for(int i=0; i<servers->machine_num; i++){
machine_t *machine = &servers->machine[i];
for(int j=0; j<machine->ip_num; j++){
printf("%s %s\n", machine->name, machine->ip[j].ip_address);
}
}
return 0;
}
// 7. pass a function pointer
typedef void (*callback_func)(char *msg);
void callback_sample(callback_func callback){
callback("Hello World !");
}
Pythonから共有ライブラリ内の関数を呼ぶ方法
さきほど用意した共有ライブラリ libmyutils.so
のそれぞれの関数をPythonからctypesを使って呼ぶ方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import ctypes
def Main():
libtest = ctypes.CDLL("./libmyutils.so")
### 1. helloworld
libtest.helloworld.argtypes = None
libtest.helloworld.restype = ctypes.c_int
libtest.helloworld()
print()
### 2. pass two values
libtest.sum.argtypes = [ctypes.c_int, ctypes.c_int]
libtest.sum.restype = ctypes.c_int
rc = libtest.sum(123, 321)
print("rc={}".format(rc))
print()
### 3. pass a pointer value
libtest.pass_value_by_reference.argtypes = [ctypes.POINTER(ctypes.c_int)]
libtest.pass_value_by_reference.restype = ctypes.c_int
myValue = ctypes.c_int(123)
rc = libtest.pass_value_by_reference(myValue)
print(myValue.value)
print("rc={}".format(rc))
print()
### 4. pass a struct by value
class pass_struct_by_value_t(ctypes.Structure):
_fields_ = [
("value", ctypes.c_int),
("str", ctypes.c_char*100)
]
libtest.pass_struct_by_value.argtypes = [pass_struct_by_value_t]
libtest.pass_struct_by_value.restype = ctypes.c_int
myStruct = pass_struct_by_value_t(123, b"Hello World !")
rc = libtest.pass_struct_by_value(myStruct)
print("rc={}".format(rc))
print()
### 5. pass a pointer struct
class pass_struct_by_reference_t(ctypes.Structure):
_fields_ = [
("value", ctypes.c_int),
("str", ctypes.c_char*100),
("result", ctypes.c_int)
]
libtest.pass_struct_by_reference.argtypes = [ctypes.POINTER(pass_struct_by_reference_t)]
libtest.pass_struct_by_reference.restype = ctypes.c_int
myStruct = pass_struct_by_reference_t(123, b"Hello World !")
rc = libtest.pass_struct_by_reference(ctypes.byref(myStruct))
print("result={}".format(myStruct.result))
print("rc={}".format(rc))
print()
### 6. pass a nested struct
class ip_t(ctypes.Structure):
_fields_ = [
("ip_address", ctypes.c_char*64)
]
class machine_t(ctypes.Structure):
_fields_ = [
("name", ctypes.c_char*128),
("ip_num", ctypes.c_int),
("ip", ctypes.POINTER(ip_t))
]
class servers_t(ctypes.Structure):
_fields_ = [
("machine_num", ctypes.c_int),
("machine", ctypes.POINTER(machine_t))
]
libtest.show_servers.argtypes = [ctypes.POINTER(servers_t)]
libtest.show_servers.restype = ctypes.c_int
machine_list = []
ip_list = []
ip_list.append(ip_t(b"192.168.0.1"))
ip_num = len(ip_list)
ips = (ip_t*ip_num)(*ip_list)
machine_list.append(machine_t(b"machineA", ip_num, ips))
ip_list.clear()
ip_list.append(ip_t(b"192.168.1.1"))
ip_list.append(ip_t(b"192.168.2.2"))
ip_list.append(ip_t(b"192.168.2.3"))
ip_num = len(ip_list)
ips = (ip_t*ip_num)(*ip_list)
machine_list.append(machine_t(b"machineB", ip_num, ips))
machine_num = len(machine_list)
machines = (machine_t*machine_num)(*machine_list)
servers = servers_t(machine_num, machines)
rc = libtest.show_servers(ctypes.byref(servers))
print("rc={}".format(rc))
print()
### 7. pass a function pointer
def callbackFunc(msg):
print("Hey, {}".format(msg.decode('utf-8')))
myCallback = ctypes.CFUNCTYPE(None, ctypes.c_char_p)
libtest.callback_sample.argtypes = [myCallback]
libtest.callback_sample.restype = None
libtest.callback_sample(myCallback(callbackFunc))
if __name__ == '__main__':
Main()