田中太郎
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
コメント