【Python】C言語で作成した関数をPythonで実行する(自動化)

C/C++
田中太郎
田中太郎

C言語で作成した関数をPythonで実行するよ!(簡単に!)

はじめに

C言語で作成した関数をPython でimport して実行できます。

詳しいやり方はこちらを参照してください。

Python とC言語を連結させるラッパーなるものが必要です。

ですが、このラッパーを作るのがちょっとめんどくさいので、自動化しました。

環境と制約

・CentOS
・Python 3系
・C言語
・gcc コンパイラ

python でインポートするモジュール
・os
・re
・sys
・subprocess
・jinja2

対応しているC言語の型
・int
・double
・char
・char*

ラッパーの自動生成

必要ファイル

・C言語の関数を書いたファイル:引数と戻り値はfloat, char, int, char* のみ対応
・c2python_template.j2:下記をコピー
・c2python.py:下記をコピー
・Python.h:/usr/include/pythonバージョン/ にあります。ないときは
sudo find / -name “Python.h” で見つかることもあります。

c2python_template.j2 コピペして保存
#include "/usr/include/python3.6m/Python.h"

{% for elements in data %}

extern {{elements[0]}} {{elements[1]}}({{elements | arg_list1}});

PyObject* f_{{elements[1]}}(PyObject* self, PyObject* args)
{
    {{elements | internal_var}}
    {% if elements[0] != "void" %}{{elements[0]}} result;{% endif %}

    if (!PyArg_ParseTuple(args, "{% for element in elements[2]%}{{element[0] | type_filter}}{% endfor %}", {{elements | arg_list2}}))
        return NULL;
    {% if elements[0] != "void" %}result = {% endif %}{{elements[1]}}({{elements | arg_list1}});
    return Py_BuildValue("{{elements[0] | type_filter}}"{% if elements[0] != "void" %},result{% endif %});
}

{% endfor %} 

static PyMethodDef methods[] = {
{% for elements in data%}
    {"{{elements[1]}}", f_{{elements[1]}}, METH_VARARGS},
{% endfor %} 
    {NULL}
};

static struct PyModuleDef {{module_name}} =
{
    PyModuleDef_HEAD_INIT,
    "{{module_name}}",
    "",
    -1,
    methods
};

PyMODINIT_FUNC PyInit_{{module_name}}(void)
{
    return PyModule_Create(&{{module_name}});
}
c2python.py コピペして保存
#!/usr/local/bin/python3
"""."""
import re
import sys
import subprocess


def initialize_jinja2(j2_file):
    """."""
    import os
    import jinja2
    j2_dir_path = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(j2_dir_path),
        trim_blocks=True,
        lstrip_blocks=True,
    )
    env.filters["type_filter"] = type_filter
    env.filters["arg_list1"] = arg_list1
    env.filters["arg_list2"] = arg_list2
    env.filters["internal_var"] = internal_var
    template = env.get_template(j2_file)
    return template


def type_filter(arg):
    if re.search(r" *int *", arg) is not None:
        return re.sub(r" *int *", "i", arg)
    elif re.search(r" *char *\* *", arg) is not None:
        return re.sub(r" *char *\* *", "s", arg)
    elif re.search(r" *char *", arg) is not None:
        return re.sub(r" *char *", "c", arg)
    elif re.search(r" *double *", arg) is not None:
        return re.sub(r" *double *", "d", arg)
    elif re.search(r" *void *", arg) is not None:
        return re.sub(r" *void *", "", arg)
    else:
        sys.exit("**ERROR**")


def arg_list1(arg):
    result = ""
    print(arg)
    for i in range(len(arg[2])):
        result += arg[2][i][1]
        if i != len(arg[2]) - 1:
            result += ","
    return result


def arg_list2(arg):
    result = ""
    for i in range(len(arg[2])):
        result += "&" + arg[2][i][1]
        if i+1 != len(arg[2]):
            result += ","
    return result


def internal_var(arg):
    result = ""
    for i in range(len(arg[2])):
        result += "{} {};\n".format(arg[2][i][0], arg[2][i][1])
    return result


def create_wrapper(j2_file, c_file, wrap_file, module_name):
    """Main."""
    function_condition = r"(((\A| +)int)|((\A| +)char\*)|((\A| +)char)|((\A| +)void)|((\A| +)double)) .+\(.*\)"
    with open(c_file, "r") as f:
        data = []
        for line in f:
            data_tmp = []
            # Exclude comment sentence.
            if re.search(r"\/\/", line) is not None:
                line = line[re.search(r"\/\/", line).start():]

            # Check error
            if re.search(function_condition, line) is None:
                continue

            # Extract return type and function name
            data_tmp.append(line[:re.search(r" .*", line).start()])
            data_tmp.append(line[re.search(r" .*", line).start()+1:re.search(r"\(.*", line).start()])
            # Extract (...)
            tmp = line[re.search(r"\(", line).end():re.search(r"\)", line).start()]
            tmp = tmp.split(",")
            data_tmp_tmp = []
            for element in tmp:
                data_tmp_tmp.append(element.rsplit(" ", 1))
            data_tmp.append(data_tmp_tmp)
            data.append(data_tmp)

    template = initialize_jinja2(j2_file)
    result = template.render(data=data, module_name=module_name)
    with open(wrap_file, "w") as f:
        f.writelines(result)


def compile_c(c_file, wrap_file, module_name):
    c_file = c_file.replace(".c","")
    wrap_file = wrap_file.replace(".c","")

    run_script = "gcc -fPIC -c -o {0}.o {0}.c"
    subprocess.run(run_script.format(c_file).split(" "))
    subprocess.run(run_script.format(wrap_file).split(" "))

    run_script = "gcc -fPIC -shared -o {0}.so {1}.o {2}.o"
    subprocess.run(run_script.format(module_name, c_file, wrap_file).split(" "))


def delete_files(c_file, wrap_file):
    c_file = c_file.replace(".c","")
    wrap_file = wrap_file.replace(".c","")

    run_script = "rm {}.o"
    subprocess.run(run_script.format(c_file).split(" "))
    subprocess.run(run_script.format(wrap_file).split(" "))


if __name__ == "__main__":
    j2_file = "c2python_template.j2"
    c_file = "c_file.c"
    wrap_file = "wrap_file.c"
    module_name = "module_name"

    create_wrapper(j2_file, c_file, wrap_file, module_name)
    compile_c(c_file, wrap_file, module_name)
    delete_files(c_file, wrap_file)
c_file.c 例

ラッパーを自動生成するため、C言語の関数の書き方には制約があります。

使える型:void, int, double, char, char*

#include <stdio.h>

int func1(int x, int y){
  return x + y;
}
 
void func2(char* s, char* t){
  printf("%s %s\n", s, t);
}

実行方法

c2python.py の if __name__ == “__main__”: 下に適当に名前を付ける

c2python.py、c2python_template.j2、c_file.cが同じディレクトリにある状態でc2python.pyを実行

ラッパーと.so ファイルができる。

あとはPython でインポートすれば、普通にCで作った関数を使えます。

タイトルとURLをコピーしました