【问题标题】:Cython bytes to C char*Cython 字节到 C char*
【发布时间】:2011-05-25 02:59:39
【问题描述】:

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

谢谢大家的帮助!

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • ...这就是为什么我昨晚应该上床睡觉的原因,因为我在过度疲劳的情况下尝试编程。事实上,让它工作的是对 strncpy 的调用(我使用它是因为输入中可能存在 NULL 字节),然后我能够调用 mcrypt_generic,将输出复制到 Python 字节串中,释放临时缓冲区,然后返回。感谢您的回答,它为我指明了正确的方向。
      • !!!如果输入中可能存在有效的 NULL 字节,strncpy不会帮助您。这意味着您的输入根本不是一个字符串,而是一个字节序列。使用memcpy 或类似的东西。
      • 哦,你说的完全正确; n 是“最多”。哦,废话。哦,哦,废话。我想您可能刚刚指出了我在另一个问题上发布的错误:stackoverflow.com/questions/4451977/…
      • 不,没有这样做。事实上,使用 memcpy 会使整个事情失败......我比以前更加困惑。叹息。
      【解决方案3】:

      在您的代码中添加一些 cmets 以帮助改进它,恕我直言。 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
      

      【讨论】:

        猜你喜欢
        • 2013-10-07
        • 2011-10-28
        • 2019-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多