5

我正在尝试为 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。但是现在我有了一个工作函数,我也将实现一个逐块的方法,这样就可以提供一个可迭代的块用于加密或解密,并且能够在没有整个源代码的情况下做到这一点并同时将所有目标都放在内存中-这样,就可以加密/解密大文件,而不必担心在任何时候在内存中最多保存三个副本...

谢谢大家的帮助!

4

3 回答 3

4

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

于 2010-12-14T08:23:00.440 回答
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' retvals 的内部char *缓冲区而不进行复制。任何修改ciphertext都会修改retval. 由于retval是一个全新的 Python str,它可以在从_real_encrypt.

有关更多详细信息,请参见此处此处的 Python C/API 文档。

最终效果为您节省了一份副本。整个代码将类似于:

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
于 2011-02-21T03:02:35.643 回答
2

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

def _real_encrypt(self,char* src):
    ...
于 2010-12-14T08:22:28.953 回答