Table of Contents

モチベーション

共有ライブラリをPythonから呼びたい

Foreign Function Interface (FFI)

一般に,あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するためには,Foreign Function Interface (FFI)を用意する必要がある.

PythonからC/C++の共有ライブラリを呼ぶ

PythonのFFIとして, ctypesPython.h , Boost.Python などがある.

ctypesはPythonに標準で組み込まれているので,importするだけで使える.

ctypesでC/C++の共有ライブラリを呼ぶ方法

共有ライブラリ内の関数の入出力に関して,以下のパターンを想定し,それぞれについてctypesから呼び出す方法を実装した.

  1. 引数が無い関数を呼ぶ例
  2. 引数が複数ある関数を呼ぶ例
  3. 関数の引数に参照渡しする例
  4. 関数の引数に構造体を値渡しする例
  5. 関数の引数に構造体を参照渡しする例
  6. 入れ子の構造体を参照渡しする例
  7. 関数ポインタを渡す例

以下は具体的な実装であり,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()