在 Cython 中使用 char** 调用方法
Calling methods using char** with Cython
我正在尝试让一些 Cython 绑定在外部 C 代码使用类型 char**
的参数的地方工作,正如通常在 main
方法中看到的那样。
不幸的是,我之前的所有尝试都失败了,而且我找不到任何关于如何实现这一目标的资源。我能找到的现有解决方案通常是指数字数组或需要重写原始代码。
如何调用使用 char**
参数的方法,最好不必修改我正在连接的底层 C 代码的调用语义?
例子
# File setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("my_test.pyx", language_level=3)
)
# File my_test.pyx
from binding cimport add as _add, main as _main
def add(a, b):
return _add(a, b)
def main(argc, argv):
cdef char[:, ::1] argv_array = [b'fixed', b'values'] + [x.encode() for x in argv]
return _main(argc + 2, &argv_array[0][0])
# File binding.pxd
cdef extern from "module1.c":
int add(int a, int b)
int main(int argc, char** argv)
// File module1.c
#include <stdio.h>
static int add(int a, int b) {
return a + b;
}
int main(int argc, char** argv) {
printf("Result: %d\n", add(40, 2));
for (int i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
return 0;
}
错误信息
(venv) user@host ~/path/to/directory $ python setup.py build_ext --inplace
Compiling my_test.pyx because it changed.
[1/1] Cythonizing my_test.pyx
Error compiling Cython file:
------------------------------------------------------------
...
return _add(a, b)
def main(argc, argv):
cdef char[:, ::1] argv_array = [x.encode() for x in argv]
return _main(argc, &argv_array[0][0])
^
------------------------------------------------------------
my_test.pyx:12:23: Cannot assign type 'char *' to 'char **'
Traceback (most recent call last):
File "setup.py", line 5, in <module>
ext_modules = cythonize("my_test.pyx", language_level=3)
File "/home/user/path/to/directory/venv/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1127, in cythonize
cythonize_one(*args)
File "/home/user/path/to/directory/venv/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1250, in cythonize_one
raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: my_test.pyx
声明 ctypedef char* cchar_tp
并将其用作 cdef cchar_tp[:, ::1] argv_array
将产生另一条错误消息:
Invalid base type for memoryview slice: cchar_tp
您面临的问题是 2D memoryview/array 不是指向指针的指针(因为这通常是存储数组的糟糕方式)。相反,它是一个单一的一维数组和一些定义维度长度的大小。请注意,char**
(表示字符串“列表”)与二维数组并不完全相同,因为通常字符串的长度不同。
因此您必须创建一个单独的指针数组,每个指针都可以指向更大的数组。 this question, which I originally marked as a duplicate, and still think is probably a duplicate 中对此进行了讨论。那里的方法应该仍然有效。
您可以使用 Python 字节对象采取一种快捷方式 - 它们可以直接分配给 const char*
。该指针将指向 Python-owned 内存,因此 bytes 对象的寿命必须比 C 指针长。在这种情况下,我通过将它们安全地存储在列表中来确保这一点。
from libc.stdlib cimport malloc, free
cdef extern from *:
"""
int m(int n, const char**) {
return 1;
}
"""
int m(int n, const char**)
def call_m():
cdef const char** to_pass
args = [b"arg1", b"arg2"]
to_pass = <const char**>malloc(sizeof(const char*)*len(args))
try:
for n, a in enumerate(args):
to_pass[n] = a # use auto-conversion from Python bytes to char*
m(len(args), to_pass)
finally:
free(to_pass)
我正在尝试让一些 Cython 绑定在外部 C 代码使用类型 char**
的参数的地方工作,正如通常在 main
方法中看到的那样。
不幸的是,我之前的所有尝试都失败了,而且我找不到任何关于如何实现这一目标的资源。我能找到的现有解决方案通常是指数字数组或需要重写原始代码。
如何调用使用 char**
参数的方法,最好不必修改我正在连接的底层 C 代码的调用语义?
例子
# File setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("my_test.pyx", language_level=3)
)
# File my_test.pyx
from binding cimport add as _add, main as _main
def add(a, b):
return _add(a, b)
def main(argc, argv):
cdef char[:, ::1] argv_array = [b'fixed', b'values'] + [x.encode() for x in argv]
return _main(argc + 2, &argv_array[0][0])
# File binding.pxd
cdef extern from "module1.c":
int add(int a, int b)
int main(int argc, char** argv)
// File module1.c
#include <stdio.h>
static int add(int a, int b) {
return a + b;
}
int main(int argc, char** argv) {
printf("Result: %d\n", add(40, 2));
for (int i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
return 0;
}
错误信息
(venv) user@host ~/path/to/directory $ python setup.py build_ext --inplace
Compiling my_test.pyx because it changed.
[1/1] Cythonizing my_test.pyx
Error compiling Cython file:
------------------------------------------------------------
...
return _add(a, b)
def main(argc, argv):
cdef char[:, ::1] argv_array = [x.encode() for x in argv]
return _main(argc, &argv_array[0][0])
^
------------------------------------------------------------
my_test.pyx:12:23: Cannot assign type 'char *' to 'char **'
Traceback (most recent call last):
File "setup.py", line 5, in <module>
ext_modules = cythonize("my_test.pyx", language_level=3)
File "/home/user/path/to/directory/venv/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1127, in cythonize
cythonize_one(*args)
File "/home/user/path/to/directory/venv/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1250, in cythonize_one
raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: my_test.pyx
声明 ctypedef char* cchar_tp
并将其用作 cdef cchar_tp[:, ::1] argv_array
将产生另一条错误消息:
Invalid base type for memoryview slice: cchar_tp
您面临的问题是 2D memoryview/array 不是指向指针的指针(因为这通常是存储数组的糟糕方式)。相反,它是一个单一的一维数组和一些定义维度长度的大小。请注意,char**
(表示字符串“列表”)与二维数组并不完全相同,因为通常字符串的长度不同。
因此您必须创建一个单独的指针数组,每个指针都可以指向更大的数组。 this question, which I originally marked as a duplicate, and still think is probably a duplicate 中对此进行了讨论。那里的方法应该仍然有效。
您可以使用 Python 字节对象采取一种快捷方式 - 它们可以直接分配给 const char*
。该指针将指向 Python-owned 内存,因此 bytes 对象的寿命必须比 C 指针长。在这种情况下,我通过将它们安全地存储在列表中来确保这一点。
from libc.stdlib cimport malloc, free
cdef extern from *:
"""
int m(int n, const char**) {
return 1;
}
"""
int m(int n, const char**)
def call_m():
cdef const char** to_pass
args = [b"arg1", b"arg2"]
to_pass = <const char**>malloc(sizeof(const char*)*len(args))
try:
for n, a in enumerate(args):
to_pass[n] = a # use auto-conversion from Python bytes to char*
m(len(args), to_pass)
finally:
free(to_pass)