Cython字节转C char*
我正在尝试写一个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文档的理解,第三行的赋值应该是把缓冲区的内容(在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数据的配合还有些不太明白。
谢谢大家的帮助!
补充说明: 这个问题其实是我自己的错误。我在熬夜太久后做这个(这是不是我们大家都经历过的事情?)结果错过了一些简单的东西。现在我有了可以工作的代码:
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 个回答
我使用的方法是(在Python 2.x中)在函数的定义中声明字符串类型的参数,这样Cython代码就会自动处理所有的转换和类型检查。
def _real_encrypt(self,char* src):
...
我想给你的代码提几点建议,希望能帮到你。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字符串对象,并用 source
的内容来初始化。第二行代码让 ciphertext
指向 retval
的内部字符缓冲区,而不是复制内容。这样,任何对 ciphertext
的修改都会影响到 retval
。因为 retval
是一个全新的Python字符串,所以可以在从 _real_encrypt
返回之前,用C代码对它进行修改。
想了解更多细节,可以查看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
第一个是把 char*
指向了 Python 字符串。第二个是分配了一块内存,但之后又把指针指向了 Python 字符串,结果就忽略了刚刚分配的那块内存。你应该是想在 Cython 中调用 C 语言的 strcpy
函数,但我不太清楚具体的细节。