2010-12-14 48 views
3

我想写一个Cython扩展到CPython来包装mcrypt库,以便我可以在Python 3中使用它。但是,我遇到了一个问题,当我尝试使用段错误其中一个mcrypt API。Cython字节到C字符*

发生故障的代码是:

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

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

+0

...这就是为什么我可能应该昨晚睡觉,因为我试图编程,而过度疲劳。事实上,它的工作原理是对strncpy(我使用的原因是输入中有NULL字节的可能性)的调用,然后我可以调用mcrypt_generic,将输出复制到Python字节串中,释放临时缓冲区,并返回。感谢这个答案,它指出了我正确的方向。 – 2010-12-14 15:16:05

+0

!!!如果输入中可以有效地使用空字节,'strncpy'将**不会**帮助你。这意味着你的输入根本不是一个字符串,而是一串字节。使用'memcpy'或类似的东西。 – 2010-12-14 15:24:52

+0

哦,你是绝对正确的; n是“高达”。哦,废话。哦,哦,废话。我想你可能只是指出我的错误,我张贴在另一个问题:http://stackoverflow.com/questions/4451977/data-corruption-wheres-the-bug – 2010-12-15 16:01:16

1

我用(与Python 2.x的)的方法是使用Cython代码做所有的转换在函数签名来声明字符串类型参数和键入时自动检查

def _real_encrypt(self,char* src): 
    ... 
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) 

上述线将创建初始化为source内容的全新的Python STR对象。第二行指向ciphertextretval的内部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