如何使用Cython将Python列表传递给C函数

时间:2021-11-15 21:21:58

I am using a Raspberry Pi to interface with custom hardware connected to the GPIO. The controlling software is written in Python, and the interface to the custom hardware is written in C, as it is a much faster C implementation. I now need to start calling my C functions from my Python, and have recently been learning how to wrap C in Cython. I have got everything to work, except passing a Python list to a C function.

我正在使用覆盆子圆周率接口自定义硬件连接到GPIO。控制软件是用Python编写的,自定义硬件的接口是用C编写的,因为它是一个更快的C实现。我现在需要从Python中调用C函数,并且最近正在学习如何在Cython中包装C。除了将Python列表传递给C函数之外,我已经完成了所有工作。

My custom hardware needs to be sent anywhere from 1 to 32 bytes, hence the use of an array.

我的自定义硬件需要从1字节发送到32字节,因此需要使用数组。

The Cython tutorials and other references I have read online either are really simple, and do not include how to pass lists to C, use numpy, which I am not using, or use very complicated code examples that lack sufficient documentation for me to understand it properly.

我在网上读过的Cython教程和其他参考资料要么非常简单,不包括如何将列表传递给C、使用我没有使用的numpy,或者使用非常复杂的代码示例,这些示例缺乏足够的文档以使我正确理解它。

What I have now are:

我现在拥有的是:

test.c

test.c

#include <stdio.h>
#include "test.h"
void pop(void) {
    a[0] = 0x55;
    a[1] = 0x66;
    a[2] = 0x77;
    a[3] = '\0';
}
void putAll(int n, char c[]) {
    memcpy(a, c, n);
}
char *getAll(void) {
    return &a[0];
}

test.h

test.h

char a[4];

void putAll(int n, char[]);
char *getAll(void);

pytest.pyx

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

defns.pxd

cdef extern from "test.h":
    char a[4]
    void pop()
    void putAll(int n, char c[])
    char *getAll()

Using the tutorials at cython.org, my getAll() and pop() functions work, but when I include the putAll() function (taken from the process_byte_data example code found at the link, under Unicode and passing strings > Accepting strings from Python code), I get this error:

使用cython.org上的教程,我的getAll()和pop()函数可以工作,但是当我包含putAll()函数时(取自链接中的process_byte_data示例代码,在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

Now, I have a way around this - combining up to 32 bytes into an int and passing as a long int, and then pulling it apart in C - but it is very ugly.

现在,我有一个方法来解决这个问题——将32个字节组合成一个int型,然后作为一个长int型,然后用C把它分开——但是它非常难看。

Also, I do not require Cython for any performance gains, other than that of using the C implemented library for interfacing with my custom hardware vs a Python implemented one.

此外,我不需要Cython获得任何性能收益,除了使用C实现库与我的自定义硬件进行交互,与使用Python实现的硬件进行交互。

Any help would be greatly appreciated.

如有任何帮助,我们将不胜感激。


(Edit) Solution

(编辑)解决方案

I managed to get this working. Here is the code I now have for anyone who needs it.

我成功地完成了这项工作。这是我现在对任何需要它的人的代码。

pytest.pyx

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

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);
}

This code is not optimal, as it uses ints in the python/cython code, then converts it to char in the C function. The pyPutAll() function in pytest.pyc accepts an ordinary python list. It then creates a C pointer and allocates memory. Iterating through the list, each value is put into a C array, and then finally passes the pointer to the C function.

这段代码不是最优的,因为它在python/cython代码中使用ints,然后在C函数中将其转换为char。在pytest中,pyPutAll()函数。pyc接受一个普通的python列表。然后它创建一个C指针并分配内存。遍历列表,将每个值放入C数组中,然后最终将指针传递到C函数。

It gets the job done, but I'm sure someone else can give a much more efficient solution.

它可以完成任务,但我相信其他人可以提供更有效的解决方案。

Matt

马特

2 个解决方案

#1


0  

ctypes is better suited to what you are trying to do.

ctypes更适合您正在尝试做的事情。

For instance: (test.py)

例如:(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

Usage:

用法:

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])

The above is for python 3 . If you're running python 2 then you can substitute bytes for str, but the function is less flexible. In addition, be aware that create_string_buffer creates a C-string (adds an additional NUL character on the end of the string).

以上是针对python 3的。如果你运行的是python 2,那么你可以用字节代替str,但是这个函数不太灵活。此外,还要注意create_string_buffer会创建一个C-string(在字符串末尾添加一个额外的NUL字符)。

To compile the shared library you need to do the following:

要编译共享库,需要执行以下操作:

gcc -fPIC -g -c -Wall test.c
gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so test.o

#2


1  

You can use Python's bytearray with Cython and I think is cleaner and easier than ctypes:

您可以使用Python的bytearray与Cython结合,我认为它比ctype更干净、更容易:

test.py

test.py

larr = bytearray([4, 1, 2])
pyPutAll(3, larr)

This works with your original putAll C function:

这与您最初的putAll C函数一起工作:

test.c

test.c

...
void putAll(int n, char c[]) {
    memcpy(a, c, n);
}
...

pytest.pyx

pytest.pyx

# Pass python bytearray to C
def pyPutAll(int n, char[:] pyc):
    defns.putAll(n, &pyc[0])

If you need to pass a list, you would have to convert it to a vector inside the pyx function, and pass a reference to that instead:

如果您需要传递一个列表,您必须将它转换为pyx函数中的一个向量,并将引用传递给它:

pytest.pyx

pytest.pyx

def pyPutAllList(int n, list pyc):
    cdef vector[char] vec = pyc
    defns.putAll(n, &vec[0])

#1


0  

ctypes is better suited to what you are trying to do.

ctypes更适合您正在尝试做的事情。

For instance: (test.py)

例如:(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

Usage:

用法:

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])

The above is for python 3 . If you're running python 2 then you can substitute bytes for str, but the function is less flexible. In addition, be aware that create_string_buffer creates a C-string (adds an additional NUL character on the end of the string).

以上是针对python 3的。如果你运行的是python 2,那么你可以用字节代替str,但是这个函数不太灵活。此外,还要注意create_string_buffer会创建一个C-string(在字符串末尾添加一个额外的NUL字符)。

To compile the shared library you need to do the following:

要编译共享库,需要执行以下操作:

gcc -fPIC -g -c -Wall test.c
gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so test.o

#2


1  

You can use Python's bytearray with Cython and I think is cleaner and easier than ctypes:

您可以使用Python的bytearray与Cython结合,我认为它比ctype更干净、更容易:

test.py

test.py

larr = bytearray([4, 1, 2])
pyPutAll(3, larr)

This works with your original putAll C function:

这与您最初的putAll C函数一起工作:

test.c

test.c

...
void putAll(int n, char c[]) {
    memcpy(a, c, n);
}
...

pytest.pyx

pytest.pyx

# Pass python bytearray to C
def pyPutAll(int n, char[:] pyc):
    defns.putAll(n, &pyc[0])

If you need to pass a list, you would have to convert it to a vector inside the pyx function, and pass a reference to that instead:

如果您需要传递一个列表,您必须将它转换为pyx函数中的一个向量,并将引用传递给它:

pytest.pyx

pytest.pyx

def pyPutAllList(int n, list pyc):
    cdef vector[char] vec = pyc
    defns.putAll(n, &vec[0])