Cython字节到C char *

时间:2010-12-14 07:33:08

标签: python python-3.x cython python-bindings

我正在尝试为CPython编写一个Cython扩展来包装mcrypt库,以便我可以将它与Python 3一起使用。但是,我遇到了一个问题,我在尝试使用其中一个mcrypt API时遇到了段错误。

失败的代码是:

def _real_encrypt(self, source):
    src_len = len(source)
    cdef char* ciphertext = source
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, src_len)
    retval = source[:src_len]
    return retval

现在,我理解Cython文档的方式,第3行的赋值应该将缓冲区(Python 3中的一个对象)的内容复制到C字符串指针。我想这也意味着它会分配内存,但是当我做了这个修改时:

def _real_encrypt(self, source):
    src_len = len(source)
    cdef char* ciphertext = <char *>malloc(src_len)
    ciphertext = source
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext, src_len)
    retval = source[:src_len]
    return retval

它仍然因为一个段错误而崩溃。它在mcrypt_generic中崩溃,但是当我使用普通的C代码时,我能够使它工作得很好,所以我不太了解Cython如何处理C数据。

感谢您的帮助!

ETA :问题是我的错误。在醒了太多时间之后我正在研究这个问题(这不是我们在某些时候都做过的事情吗?)并且错过了一些愚蠢的事情。我现在拥有的代码是:

def _real_encrypt(self, source):
    src_len = len(source)
    cdef char *ciphertext = <char *>malloc(src_len)
    cmc.strncpy(ciphertext, source, src_len)
    cmc.mcrypt_generic_init(self._mcStream, <void *>self._key,
                            len(self._key), NULL)
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext,
                       src_len)

    retval = ciphertext[:src_len]
    cmc.mcrypt_generic_deinit(self._mcStream)
    return retval

它可能不是世界上最有效的代码,因为它创建了一个副本来进行加密,然后第二个副本返回到返回值。我不确定是否可以避免这种情况,因为我不确定是否可以采用新分配的缓冲区并将其作为字节串返回到Python就地。但是现在我有一个工作函数,我也将实现一个逐块的方法,这样就可以提供一个可迭代的块来加密或解密,并且能够在没有整个源的情况下完成它。并且目的地全部都在内存中 - 这样,就可以加密/解密大文件,而不必担心在任何一个点上最多可以在内存中保存三个副本......

感谢大家的帮助!

3 个答案:

答案 0 :(得分:4)

第一个是将char*指向Python字符串。第二个分配内存,但随后将指针重新指向Python字符串并忽略新分配的内存。你应该从Cython中调用C库函数strcpy,大概是这样的;但我不知道细节。

答案 1 :(得分:3)

有关您的代码的一些评论,以帮助改进它,恕我直言。 python C API提供了一些功能,它们可以完全满足您的需要,并确保一切都符合Python的工作方式。它将处理嵌入式NULL没有问题。

不要直接调用malloc,而是更改:

cdef char *ciphertext = <char *>malloc(src_len)

cdef str retval = PyString_FromStringAndSize(PyString_AsString(source), <Py_ssize_t>src_len)
cdef char *ciphertext = PyString_AsString(retval)

以上几行将创建一个全新的Python str对象,初始化为source的内容。第二行将ciphertext指向retval的内部char *缓冲区而不进行复制。修改ciphertext的任何内容都将修改retval。由于retval是一个全新的Python str,因此可以在从_real_encrypt返回之前通过C代码进行修改。

有关详细信息,请参阅上述函数的Python C / API文档,herehere

净效果可以为您节省一份副本。整个代码类似于:

cdef extern from "Python.h":
    object PyString_FromStringAndSize(char *, Py_ssize_t)
    char *PyString_AsString(object)

def _real_encrypt(self, source):
    src_len = len(source)
    cdef str retval = PyString_FromStringAndSize(PyString_AsString(source), <Py_ssize_t>src_len)
    cdef char *ciphertext = PyString_AsString(retval)
    cmc.mcrypt_generic_init(self._mcStream, <void *>self._key,
                            len(self._key), NULL)
    cmc.mcrypt_generic(self._mcStream, <void *>ciphertext,
                       src_len)
    # since the above initialized ciphertext, the retval str is also correctly initialized, too.
    cmc.mcrypt_generic_deinit(self._mcStream)
    return retval

答案 2 :(得分:2)

我使用的方法(使用Python 2.x)是在函数签名中声明字符串类型参数,以便Cython代码自动完成所有转换和类型检查

def _real_encrypt(self,char* src):
    ...