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

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

## インストール

[この記事](https://qiita.com/milmilk/items/f1d08cd2c3c72ebadb17)を参考しインストールしました。
自分的な手順は以下通りです。(Mac環境、Python3.7)
### 1. brewでBoost.Pythonをインストール
```bash
brew install boost-python3
```
### 2. シンボリックリンク
インストールしたら `/usr/local/Cellar/`にある Boost.PythonのdylibとPython自体を実行するディレクトリにリンクします。

```bash
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のパスの確認

```bash
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`を以下の行を追加します：

``` bash:~/.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) 反映させます

```bash
source ~/.bashrc
```

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

```c:my_c.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void hello_world();
```

```c:my_c.c
#include "my_c.h"

void hello_world() {
  printf("hello world\n");
}
```

```cpp: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);
}
```

```python: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
```

これでできます

```bash
$ make
$ python my_python.py
hello world
```

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

```c: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);
```

```c: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;
}
```

```cpp: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);
}
```

```python: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で書き直せずに使えたのはすごく嬉しかったです。