Cで作った関数をpythonで呼ぶ

# 3行で
* 普段はpythonを使うが、やむにやまれぬ事情でC言語で書かれたリソースを使う必要はまま生じる
* pythonはCを使って新たなモジュールを追加することができるので、その機能を使えばいい
* Python.hに用意された特別な構造体を使ってCでモジュール名、メソッド、引数を定義し、[distutils.core.setup](https://docs.python.org/ja/3/distutils/setupscript.html)(pythonのメソッド!!)を使ってビルドする


# 必要な作業
1. C言語で書かれたソースコードのベースを用意
2. C言語でラッパーを記述
3. pythonでsetupスクリプトを記述
4. setupスクリプトを実行

# 細かいことはいいから答えを教えろ
ソースコードと使い方だけ書いて下記のリポジトリにアップロードしときました。
https://github.com/nagiton/c-python-api

# 目標
自作のモジュールhelloをC言語で作成し、下記のような出力を実現する。

```bash:
sh-4.2$ python 
Python 3.6.10 |Anaconda, Inc.| (default, Mar 23 2020, 23:13:11) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.add(2,3)
5
>>> hello.out('tokyo','koike')
こんにちは、私は tokyo の koike です。
>>> 
```

# C言語で書かれたソースコードのベースを用意
今回ではシンプルなものだけを使います。
まずはシンプルに始めるのがよいかと。

``` c:hello.c
// hello.c
int add(int x, int y)
{
	return x + y;
}

void out(const char* adrs, const char* name)
{
	printf("こんにちは、私は %s の %s です。\n", adrs, name);
}
```
2つの関数を定義しているだけのソースコードです。

* add: 入力される2つのint型の数値の和を返します。戻り値はintです。
* out: 入力される2つの文字列adrs、nameをとり、標準出力に「こんにちは、私は(adrsの中身)の(nameの中身)です。」を表示します。戻り値なし。

`char*`ってなんだよこれだからC言語はよお！💢💢💢💢💢という人は
→[１０－３．ポインタと文字列](http://www9.plala.or.jp/sgwr-t/c/sec10-3.html)


# C言語でラッパーを記述
まず、ソースコード例を示し、順に解説します。

```c:helloWrapper.c
// helloWrapper.c
#include "Python.h"

extern int add(int, int);
extern void out(const char*, const char*);

//definition of add method
static PyObject* hello_add(PyObject* self, PyObject* args)
{
	int x, y, g;

	if (!PyArg_ParseTuple(args, "ii", &x, &y))
		return NULL;
	g = add(x, y);
	return Py_BuildValue("i", g);
}

//definition of out method
static PyObject* hello_out(PyObject* self, PyObject* args, PyObject* kw)
{
	const char* adrs = NULL;
	const char* name = NULL;
	static char* argnames[] = {"adrs", "name", NULL};

	if (!PyArg_ParseTupleAndKeywords(args, kw, "|ss",
			argnames, &adrs, &name))
		return NULL;
	out(adrs, name);
	return Py_BuildValue("");
}

//definition of all methods of my module
static PyMethodDef hellomethods[] = {
	{"add", hello_add, METH_VARARGS},
	{"out", hello_out, METH_VARARGS | METH_KEYWORDS},
	{NULL},
};

// hello module definition struct
static struct PyModuleDef hello = {
    PyModuleDef_HEAD_INIT,
    "hello",
    "Python3 C API Module(Sample 1)",
    -1,
    hellomethods
};

//module creator
PyMODINIT_FUNC PyInit_hello(void)
{
    return PyModule_Create(&hello);
}
```
ラッパーで必要な処理は大きく分けて、以下のようなブロックに分かれます

* Python.hのinclude
* pythonで使いたい外部関数の宣言
* メソッドの定義
    * pythonオブジェクトを入出力にもつ関数の宣言
    * pythonオブジェクトからC言語の変数に変換
    * 処理
    * C言語の変数からpythonオブジェクトに変換して出力
* モジュールに入れる全てのメソッドのリストアップ
* モジュールの作成

## include
必ず最初に`Python.h`をインクルードします。公式によると
>注釈 Python は、システムによっては標準ヘッダの定義に影響するようなプリプロセッサ定義を行っているので、 Python.h をいずれの標準ヘッダよりも前にインクルード せねばなりません 。
Python.h をインクルードする前に、常に PY_SSIZE_T_CLEAN を定義することが推奨されます。 このマクロの解説については 拡張モジュール関数でのパラメタ展開 を参照してください。
https://docs.python.org/ja/3/extending/extending.html#a-simple-example

とのことです。

## extern hogehoge
helloWrapper.cの外で定義された関数を使うときにこのように表現します。
`global`/`private`の関係のようなものが`extern`/`static`にあります。
（実は`extern`はつけてもつけなくても勝手に`extern`になるようです。これだからC言語はよお💢）
hogehoge部分はhello.cの関数の宣言と同じですね。

## メソッドの定義
大まかに言って、処理の流れは
1. `PyObject`型(C言語でpythonのオブジェクトを扱う際の型)で引数を受け取り、`PyObject`型で返す関数を宣言する
2. `PyObject`型からC言語で扱える型に`PyArg_ParseTuple`などで変換する
3. C言語で扱える型を使って処理を行う
4. `Py_BuildValue`で`PyObject`型に戻す

といった感じになります。


### 関数定義
```c:
static PyObject* 関数名
```
で、`PyObject`型の参照を返す関数であることを宣言しています。`PyObject`とは

>全てのオブジェクト型はこの型を拡張したものです。 この型には、あるオブジェクトを指すポインタをオブジェクトとして Python から扱うのに必要な情報が入っています。 通常の 「リリース」 ビルドでは、この構造体にはオブジェクトの参照カウントとオブジェクトに対応する型オブジェクトだけが入っています。 実際には PyObject であることは宣言されていませんが、全ての Python オブジェクトへのポインタは PyObject* へキャストできます。 メンバにアクセスするには Py_REFCNT マクロと Py_TYPE マクロを使わなければなりません。
https://docs.python.org/ja/3.5/c-api/structures.html#c.PyObject

ということで、C言語内でpythonに渡すオブジェクトはみんなこの型を拡張した型を使います。
ここで定義した関数の中で`PyObject`型の構造体を作り、その構造体への参照を最終的に外に渡すわけですね。参照渡しです。

```c:
hello_add(PyObject* self, PyObject* args)
```
なので、中身も参照渡しです。
`self`はpythonから実行する時のモジュール自身、`args`はメソッドに渡される位置引数を表します。
（参考: [用語集](https://docs.python.org/ja/3.5/glossary.html)）

### 関数の処理とpythonオブジェクトからCの変数への変換
```c:
	int x, y, g;

	if (!PyArg_ParseTuple(args, "ii", &x, &y))
		return NULL;
	g = add(x, y);
```
`if`文以外は、C言語の入門まででもやったことがあれば見慣れた感じです。
`if`でやっていることはpythonから入力される変数の型を調べ、可能ならCで扱う変数に変換し、不可能なら例外を吐くという処理です。
**pythonから値を受け取るときは`PyArg_ParseTuple`でC言語の変数に変換できます。**
（参考:[引数の解釈と値の構築](https://docs.python.org/ja/3/c-api/arg.html#c.PyArg_ParseTuple))

python上で`hello.add(2,3)`のように渡される引数が文字列`'ii'`、つまり2つのint型と解釈できるとき、`x` `y`に代入します。
その後関数`add`に渡して答え`g`を得ます。

### Cの型からpythonのオブジェクトに戻して出力
```c:
return Py_BuildValue("i", g);
```
`Py_BuildValue`でC言語の変数である`g`をpythonのオブジェクト`PyObject`に変換して返しています。
`int`型なので`'i'`を指定していますが、型に合った文字列を指定してください。

### 別の例
`hello_out`はキーワード変数を使う例になっています。
`PyArg_ParseTuple`の代わりに`PyArg_ParseTupleAndKeywords`を使うなどの違いがありますが

## モジュールに入れる全てのメソッドのリストアップ
```c:
//definition of all methods of my module
static PyMethodDef hellomethods[] = {
	{"add", hello_add, METH_VARARGS},
	{"out", hello_out, METH_VARARGS | METH_KEYWORDS},
	{NULL},
};
```
ここでモジュールに入れる全てのメソッドをリストアップして、そのインターフェイスを`PyMethodDef`型で記述します。
構造体に値を入れるときは定義された順番と同じ順番で並べる必要があります。

[PyMethodDef](https://docs.python.org/ja/3/c-api/structures.html#c.PyMethodDef)
上記によれば
メソッド名、C 実装へのポインタ、呼び出しをどのように行うかを示すフラグビット、docstring の内容を指すポインタ
の順で並べておけばいいみたいですね。

最後のNULLは・・・・、ごめんなさい(これだからC言語はよお💢)

## モジュールの作成
```c:
// myModule definition struct
static struct PyModuleDef hello = {
    PyModuleDef_HEAD_INIT,
    "hello",
    "Python3 C API Module(Sample 1)",
    -1,
    hellomethods
};

//module creator
PyMODINIT_FUNC PyInit_hello(void)
{
    return PyModule_Create(&hello);
}
```
`PyMethodDef`型でモジュールの情報を格納します。
構造体に値を入れるときは定義された順番と同じ順番で並べる必要があるのでドキュメントを確認すると
[PyModuleDef](https://docs.python.org/ja/3/c-api/module.html#c.PyModuleDef)
PyModuleDef_HEAD_INIT、モジュール名、Docstring、モジュールの状態、`PyMethodDef`型で定義したメソッド
の順で入れておけばOKです。

そして最終的に`PyMODINIT_FUNC`型を返す関数を作ります。
[PyModule_Create](https://docs.python.org/ja/3/c-api/module.html#c.PyModule_Create)
ここは単に`PyMethodDef`型で定義されたモジュールの定義を使って実際にモジュールを作る関数を叩けばOKです。


# pythonでsetupスクリプトを記述
pythonを普段使っていると、makefileを見るたびに蕁麻疹がでます・・・。
ここで書いたラッパーをgccでコンパイルすることも・・・できなかないですが・・・
私はあいつが嫌いなので、違う方法をとります。
幸いに、pythonにはモジュール開発者のためにDistutilsというものがあります！！！！

```python:setup.py
from distutils.core import setup, Extension

module1 = Extension('hello',
                    sources = ['helloWrapper.c','hello.c'],
                    #extra_objects = ['hello.o'],
                    )

setup(name = 'hello', version = '1.0.0', ext_modules = [module1])
```

細かく説明しなくても、pythonならわかるよね！！！！！やっぱりpythonなんだよなあ。この実家のような安心感よ。

ちょっとだけ補足しておくと、.cファイルが複数に渡る場合、sourcesでリストで指定してください。作業ディレクトリからの相対パスでも絶対パスでもOKです。
もしあなたがそうしたければ、gccかなにかで作った中間生成物の.o(オブジェクトファイル)を使うこともできます。
この場合はextra_objectsに指定してください。
他のオプションは[class distutils.core.Extension](https://docs.python.org/ja/3/distutils/apiref.html#distutils.core.Extension)参照のこと。
makefileでやってることはここで指定できるのでは？(makefileに詳しくない)

# setupスクリプトを実行
hello.c、helloWrapper.c、setup.pyがあるディレクトリに移動し、

```bash:
python setup.py install
```
するとpythonでhelloがimportできるようになります。
やったね！！！

# 参考
[C や C++ による Python の拡張(公式)](https://docs.python.org/ja/3/extending/extending.html)
[setup スクリプトを書く(公式)](https://docs.python.org/ja/3/distutils/setupscript.html)
[API リファレンス(distutils)](https://docs.python.org/ja/3/distutils/apiref.html)
[【Python C API入門】C/C++で拡張モジュール作ってPythonから呼ぶ -前編-](https://cpp-learning.com/python_c_api_step1/)
[PythonからCプログラムを呼び出す](http://owa.as.wakwak.ne.jp/zope/docs/Python/BindingC/)

本記事のソースコード
https://github.com/nagiton/c-python-api