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

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

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

はじめに

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

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

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

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

環境と制約

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

python でインポートする標準以外のモジュール
・jinja2

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

ラッパーの自動生成

必要ファイル

・c_file.c(C言語の関数を書いたファイル):引数と戻り値はdouble, 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 argparse
import os
import re
import subprocess
import sys

import jinja2

def arg_parser(module_name_default):
    """Parser."""
    parser = argparse.ArgumentParser(description="")
    parser.add_argument("c_file")
    parser.add_argument("--name", default=module_name_default)
    return parser.parse_args()

def initialize_jinja2(j2_file):
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(os.path.dirname(j2_file)),
        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(os.path.basename(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 = ""
    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):
    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)

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

def compile_c(c_file, wrap_file, module_name):
    c_file = re.sub(r"\.c$", "", c_file)
    wrap_file = re.sub(r"\.c$", "", wrap_file)

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

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

def delete_files(c_file, wrap_file):
    c_file = re.sub(r"\.c$", "", c_file)
    wrap_file = re.sub(r"\.c$", "", wrap_file)

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

def initialize(args):
    for i in args:
        if os.path.exists(i):
            continue
        sys.exit("ERROR: {} is not found.".format(i))

def main(j2_file, wrap_file, c_file, module_name):
    initialize([j2_file])
    create_wrapper(j2_file, c_file, wrap_file, module_name)
    compile_c(c_file, wrap_file, module_name)
    delete_files(c_file, wrap_file)

if __name__ == "__main__":
    j2_file = "c2python_template.j2"
    wrap_file = "wrap_file.c"
    module_name_default = "my_func"
    args = arg_parser(module_name_default)
    main(
        os.path.join(os.path.dirname(__file__), j2_file),
        wrap_file,
        args.c_file,
        args.name,
    )
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、c2python_template.j2を同じディレクトリに置きます。

あとは下のコマンドを実行します

./c2python.py c_file.c

デフォルトでmy_func.so ができます

オプションで-name <名前>で自由に変更可能です

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

import my_func

コメント

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