繁体   English   中英

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

[英]How to pass Python list to C function using Cython

我正在使用 Raspberry Pi 与连接到 GPIO 的自定义硬件进行交互。 控制软件写在 Python 中,自定义硬件的接口写在 C 中,因为它是一个更快的 C 实现。 我现在需要开始从我的 Python 调用我的 C 函数,并且最近一直在学习如何在 Cython 中包装 C。 除了将 Python 列表传递给 C function 之外,我已经做好了一切工作。

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

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

我现在拥有的是:

测试.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];
}

测试.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() function(取自链接中的 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

现在,我有办法解决这个问题 - 将最多 32 个字节组合成一个 int 并作为 long int 传递,然后在 C 中将它分开 - 但它非常难看。

此外,我不需要 Cython 来获得任何性能提升,除了使用 C 实现的库与我的自定义硬件接口与 Python 实现的库相比。

您可以在Cython中使用Python的字节数组,我认为它比ctypes更干净,更容易:

test.py

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

这适用于您原始的putAll 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])

我设法让这个工作。 这是我现在为任何需要它的人提供的代码。

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 function 中的 char。pytest.pyc 中的pyPutAll() function 接受普通的 python 列表。 然后创建一个C指针,并分配memory。遍历链表,将每个值放入一个C数组,最后将指针传递给C function。

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


这个答案作为对问题的编辑发布了如何通过CC BY-SA 3.0 下的 OP Hengy使用 Cython 将 Python 列表传递给 C function。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM