如何使用 Cython 将 Python 列表传递给 C 函数
How to pass Python list to C function using Cython
我正在使用 Raspberry Pi 与连接到 GPIO 的自定义硬件进行交互。控制软件是用 Python 编写的,自定义硬件的接口是用 C 编写的,因为它是一个更快的 C 实现。我现在需要开始从我的 Python 调用我的 C 函数,并且最近一直在学习如何在 Cython 中包装 C。除了将 Python 列表传递给 C 函数外,我已经做好了一切工作。
我的自定义硬件需要发送 1 到 32 个字节的任意位置,因此使用数组。
我在网上阅读的 Cython 教程和其他参考资料非常简单,并且不包括如何将列表传递给 C,如何使用我没有使用的 numpy,或者使用缺乏足够文档的非常复杂的代码示例让我正确理解它。
我现在拥有的是:
test.c
#include <stdio.h>
#include "test.h"
void pop(void) {
a[0] = 0x55;
a[1] = 0x66;
a[2] = 0x77;
a[3] = '[=11=]';
}
void putAll(int n, char c[]) {
memcpy(a, c, n);
}
char *getAll(void) {
return &a[0];
}
test.h
char a[4];
void putAll(int n, char[]);
char *getAll(void);
pytest.pyx
cimport defns
# Populate C array with values
def pypop():
defns.pop()
# Pass python list to C
def pyPutAll(int n, char[:] pyc):
cdef char* c = pyc
defns.putAll(n, c)
# Get array from C
def pyGetAll():
cdef char* c = defns.getAll()
cdef bytes pyc = c
print pyc
defns.pxd
cdef extern from "test.h":
char a[4]
void pop()
void putAll(int n, char c[])
char *getAll()
使用 cython.org 上的教程,我的 getAll() 和 pop() 函数可以工作,但是当我包含 putAll() 函数时(取自 process_byte_data 示例代码,在 link,在 Unicode 下并传递字符串 > 从 Python 代码接受字符串),我收到此错误:
python setup.py build_ext -i
Error compiling Cython file:
------------------------------------------------------------
...
def pyputAll(int n, char[:] pyc):
^
------------------------------------------------------------
pytest.pyx:13:25: Expected an identifier or literal
现在,我有办法解决这个问题 - 将最多 32 个字节组合成一个 int 并作为 long int 传递,然后在 C 中将它分开 - 但它非常难看。
此外,我不需要 Cython 来获得任何性能提升,除了使用 C 实现的库与我的自定义硬件接口与 Python 实现的库相比。
如有任何帮助,我们将不胜感激。
(编辑)解决方案
我设法让它工作了。这是我现在为任何需要它的人提供的代码。
pytest.pyx
...
def pyPutAll(int n, c):
cdef int *ptr
ptr = <int *>malloc(n*cython.sizeof(int))
if ptr is NULL:
raise MemoryError()
for i in xrange(n):
ptr[i] = c[i]
defns.putAll(n, ptr)
free(ptr)
...
test.c
void putAll(int n, int c[])
{
char d[n];
int i;
for (i=0;i<n;i++) {
d[i] = c[i];
}
memcpy(addr, d, n);
}
这段代码不是最优的,因为它在 python/cython 代码中使用了 int,然后在 C 函数中将其转换为 char。 pytest.pyc 中的 pyPutAll()
函数接受一个普通的 python 列表。然后它创建一个 C 指针并分配内存。遍历列表,每个值放入一个C数组,最后将指针传递给C函数。
它完成了工作,但我相信其他人可以提供更有效的解决方案。
马特
ctypes
更适合您的目标。
例如:(test.py)
from ctypes import create_string_buffer, c_char_p, c_int, CDLL
libtest = CDLL("./libtest.so")
_putAll = libtest.putAll
_putAll.restype = None
_putAll.argtypes = [c_int, c_char_p]
def putAll(values):
"""Expects a bytes object, bytearray, or a list of ints."""
char_buffer = create_string_buffer(bytes(values))
_putAll(len(char_buffer), char_buffer)
getAll = libtest.getAll
getAll.restype = c_char_p
getAll.argtypes = None
用法:
import test
test.putAll(b"hello world")
assert test.getAll() == b"hello world"
test.putAll(bytearray(b"another string"))
test.putAll([1, 2, 3, 255])
以上是 python 3 。如果你是 运行 python 2 那么你可以用 bytes
代替 str
,但是这个函数不太灵活。此外,请注意 create_string_buffer
创建一个 C 字符串(在字符串末尾添加一个额外的 NUL 字符)。
要编译共享库,您需要执行以下操作:
gcc -fPIC -g -c -Wall test.c
gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so test.o
您可以将 Python 的 bytearray 与 Cython 一起使用,我认为它比 ctypes 更干净、更容易:
test.py
larr = bytearray([4, 1, 2])
pyPutAll(3, larr)
这适用于您原来的 putAll C 函数:
test.c
...
void putAll(int n, char c[]) {
memcpy(a, c, n);
}
...
pytest.pyx
# Pass python bytearray to C
def pyPutAll(int n, char[:] pyc):
defns.putAll(n, &pyc[0])
如果您需要传递一个列表,则必须在 pyx 函数中将其转换为一个向量,并改为传递对该列表的引用:
pytest.pyx
def pyPutAllList(int n, list pyc):
cdef vector[char] vec = pyc
defns.putAll(n, &vec[0])
我正在使用 Raspberry Pi 与连接到 GPIO 的自定义硬件进行交互。控制软件是用 Python 编写的,自定义硬件的接口是用 C 编写的,因为它是一个更快的 C 实现。我现在需要开始从我的 Python 调用我的 C 函数,并且最近一直在学习如何在 Cython 中包装 C。除了将 Python 列表传递给 C 函数外,我已经做好了一切工作。
我的自定义硬件需要发送 1 到 32 个字节的任意位置,因此使用数组。
我在网上阅读的 Cython 教程和其他参考资料非常简单,并且不包括如何将列表传递给 C,如何使用我没有使用的 numpy,或者使用缺乏足够文档的非常复杂的代码示例让我正确理解它。
我现在拥有的是:
test.c
#include <stdio.h>
#include "test.h"
void pop(void) {
a[0] = 0x55;
a[1] = 0x66;
a[2] = 0x77;
a[3] = '[=11=]';
}
void putAll(int n, char c[]) {
memcpy(a, c, n);
}
char *getAll(void) {
return &a[0];
}
test.h
char a[4];
void putAll(int n, char[]);
char *getAll(void);
pytest.pyx
cimport defns
# Populate C array with values
def pypop():
defns.pop()
# Pass python list to C
def pyPutAll(int n, char[:] pyc):
cdef char* c = pyc
defns.putAll(n, c)
# Get array from C
def pyGetAll():
cdef char* c = defns.getAll()
cdef bytes pyc = c
print pyc
defns.pxd
cdef extern from "test.h":
char a[4]
void pop()
void putAll(int n, char c[])
char *getAll()
使用 cython.org 上的教程,我的 getAll() 和 pop() 函数可以工作,但是当我包含 putAll() 函数时(取自 process_byte_data 示例代码,在 link,在 Unicode 下并传递字符串 > 从 Python 代码接受字符串),我收到此错误:
python setup.py build_ext -i
Error compiling Cython file:
------------------------------------------------------------
...
def pyputAll(int n, char[:] pyc):
^
------------------------------------------------------------
pytest.pyx:13:25: Expected an identifier or literal
现在,我有办法解决这个问题 - 将最多 32 个字节组合成一个 int 并作为 long int 传递,然后在 C 中将它分开 - 但它非常难看。
此外,我不需要 Cython 来获得任何性能提升,除了使用 C 实现的库与我的自定义硬件接口与 Python 实现的库相比。
如有任何帮助,我们将不胜感激。
(编辑)解决方案
我设法让它工作了。这是我现在为任何需要它的人提供的代码。
pytest.pyx
...
def pyPutAll(int n, c):
cdef int *ptr
ptr = <int *>malloc(n*cython.sizeof(int))
if ptr is NULL:
raise MemoryError()
for i in xrange(n):
ptr[i] = c[i]
defns.putAll(n, ptr)
free(ptr)
...
test.c
void putAll(int n, int c[])
{
char d[n];
int i;
for (i=0;i<n;i++) {
d[i] = c[i];
}
memcpy(addr, d, n);
}
这段代码不是最优的,因为它在 python/cython 代码中使用了 int,然后在 C 函数中将其转换为 char。 pytest.pyc 中的 pyPutAll()
函数接受一个普通的 python 列表。然后它创建一个 C 指针并分配内存。遍历列表,每个值放入一个C数组,最后将指针传递给C函数。
它完成了工作,但我相信其他人可以提供更有效的解决方案。
马特
ctypes
更适合您的目标。
例如:(test.py)
from ctypes import create_string_buffer, c_char_p, c_int, CDLL
libtest = CDLL("./libtest.so")
_putAll = libtest.putAll
_putAll.restype = None
_putAll.argtypes = [c_int, c_char_p]
def putAll(values):
"""Expects a bytes object, bytearray, or a list of ints."""
char_buffer = create_string_buffer(bytes(values))
_putAll(len(char_buffer), char_buffer)
getAll = libtest.getAll
getAll.restype = c_char_p
getAll.argtypes = None
用法:
import test
test.putAll(b"hello world")
assert test.getAll() == b"hello world"
test.putAll(bytearray(b"another string"))
test.putAll([1, 2, 3, 255])
以上是 python 3 。如果你是 运行 python 2 那么你可以用 bytes
代替 str
,但是这个函数不太灵活。此外,请注意 create_string_buffer
创建一个 C 字符串(在字符串末尾添加一个额外的 NUL 字符)。
要编译共享库,您需要执行以下操作:
gcc -fPIC -g -c -Wall test.c
gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so test.o
您可以将 Python 的 bytearray 与 Cython 一起使用,我认为它比 ctypes 更干净、更容易:
test.py
larr = bytearray([4, 1, 2])
pyPutAll(3, larr)
这适用于您原来的 putAll C 函数:
test.c
...
void putAll(int n, char c[]) {
memcpy(a, c, n);
}
...
pytest.pyx
# Pass python bytearray to C
def pyPutAll(int n, char[:] pyc):
defns.putAll(n, &pyc[0])
如果您需要传递一个列表,则必须在 pyx 函数中将其转换为一个向量,并改为传递对该列表的引用:
pytest.pyx
def pyPutAllList(int n, list pyc):
cdef vector[char] vec = pyc
defns.putAll(n, &vec[0])