6
8

More than 3 years have passed since last update.

Boost.Pythonを使ってC言語のラッパーを作る

Last updated at Posted at 2019-12-28

PythonでCの関数を呼べたのでメモを書きます。
Cのラッパーを作るときはいくつかの方法がありますが、今回はBoost.Pythonを選びました。

インストール

この記事を参考しインストールしました。
自分的な手順は以下通りです。(Mac環境、Python3.7)

1. brewでBoost.Pythonをインストール

brew install boost-python3

2. シンボリックリンク

インストールしたら /usr/local/Cellar/にある Boost.PythonのdylibとPython自体を実行するディレクトリにリンクします。

ln -s /usr/local/Cellar/boost-python3/1.71.0_1/lib/libboost_python37.dylib /path/to/directory/libboost_python37.dylib
ln -s /usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/Python /path/to/directory/libpython3.7.dylib

3. pyconfig.hがincludeできないとエラーが出た時の対処法

最初にコンパイルしたときに、
fatal error: pyconfig.h: No such file or directoryが出たので以下の手順で解決できました。

(1)Pythonのincludesのパスの確認

python3.7-config --includes --libs

(2)下記のような情報が出てきます。一番目を選びます(この例は -I/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m

-I/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m -I/home/victor/anaconda3/include/python3.7m -lpython3.7m -lpthread -ldl -lutil -lrt -lm

(3) -Iを除いたパスで ~/.bashrcを以下の行を追加します:

~/.bashrc
export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m"

(4) 反映させます

source ~/.bashrc

Hello World

Boost.PythonはC++のライブラリなので、Cを使うときには、別途でC++を用意してCの内容をincludeします。
C、C++、PythonのファイルとMakefileを用意します:

my_c.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void hello_world();
my_c.c
#include "my_c.h"

void hello_world() {
  printf("hello world\n");
}
my_cpp.cpp
#include <boost/python.hpp>
extern "C" {
  #include "my_c.h"
}
using namespace boost::python;

BOOST_PYTHON_MODULE(my_wrapper){
    def("hello_world", hello_world);
}
my_python.py
import my_wrapper

my_wrapper.hello_world()
Makefile
wrapper: my_cpp.cpp
  gcc -c -o my_c.o my_c.c
  g++ -c -o my_cpp.o my_cpp.cpp
  g++ -fPIC -Wall -O2 -shared -o my_wrapper.so my_c.o my_cpp.o libboost_python37.dylib libpython3.7.dylib

これでできます

$ make
$ python my_python.py
hello world

CのstructやarrayとPythonのdictやlistの変換

シンプルな関数だと特に別の処理がいりませんが、お互いに知らないtypeだと変換は必要そうです。
説明の代わりにサンプルコードになります。

my_c.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  int a;
  int b;
}my_struct;

my_struct my_sum(my_struct *my_array, int len);
my_c.c
#include "my_c.h"

my_struct my_sum(my_struct *my_array, int len) {
  my_struct sum = { 0 };
  for(int i = 0; i < len; i++) {
    sum.a += my_array[i].a;
    sum.b += my_array[i].b;
  }
  return sum;
}
my_cpp.cpp
#include <boost/python.hpp>
extern "C" {
  #include "my_c.h"
}
using namespace boost::python;

// boost::python::dict, boost::python::list, boost::python::lenなどがあります
dict wrapper_sum(list py_list) {
  int length = len(py_list);
  my_struct ms[length];
  for(int i = 0; i < length; i++) {
    dict py_dict = extract<dict>(py_list[i]);
    ms[i].a = extract<int>(py_dict["a"]);
    ms[i].b = extract<int>(py_dict["b"]);
  }
  dict py_res;
  my_struct c_res = my_sum(ms, length);
  py_res["a"] = c_res.a;
  py_res["b"] = c_res.b;
  return py_res;
}

BOOST_PYTHON_MODULE(my_wrapper){
    def("my_sum", wrapper_sum);
}
my_python.py
import my_wrapper

data = [
    {
        "a": 3,
        "b": 9
    },
    {
        "a": 1,
        "b": 3
    },
    {
        "a": 6,
        "b": 2
    },
    {
        "a": 0,
        "b": 4
    }
]

res = my_wrapper.my_sum(data)
print(res)

実行します:

$ make
$ python my_python.py
{'a': 10, 'b': 18}

結論

少し工夫すれば、速度のためにCで書いた関数は、
Pythonで書き直せずに使えたのはすごく嬉しかったです。

6
8
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
6
8