2010-12-14 2 views
3

Я пытаюсь написать расширение Cython для CPython, чтобы обернуть библиотеку mcrypt, чтобы я мог использовать его с Python 3. Однако я столкнулся с проблемой, когда я segfault при попытке использовать один из API-интерфейсов mcrypt.Cython bytes to C char *

Код, который не удается является:

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 

он все еще разбился с Segfault. Это сбой внутри 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 на месте как bytestring. Но теперь, когда у меня есть рабочая функция, я собираюсь реализовать поблочный метод, так что можно обеспечить итерабельность блоков для шифрования или дешифрования и иметь возможность сделать это, не имея всего источника и назначения все в памяти все сразу - таким образом, можно было бы зашифровать/дешифровать огромные файлы, не беспокоясь о том, чтобы удерживать до трех копий в памяти в любой точке ...

Спасибо всем за помощь!

ответ

3

Первый указывает на char* на строку Python. Второй выделяет память, но затем перенаправляет указатель на строку Python и игнорирует вновь выделенную память. Вы должны вызвать функцию библиотеки C strcpy из Cython, предположительно; но я не знаю деталей.

+0

... вот почему я, вероятно, должен был пойти спать прошлой ночью, потому что я пытался запрограммировать, пока переутомил. В самом деле, что заставило его работать, был вызов strncpy (который я использовал из-за возможности NULL-байтов на входе), а затем я смог выполнить вызов mcrypt_generic, скопировать вывод в Python, временный буфер и возврат. Спасибо за этот ответ, он указал мне в правильном направлении. –

+0

!!! 'strncpy' будет ** не ** помочь вам, если на входе могут быть NULL байты. Это означает, что ваш ввод не является действительно строкой, а последовательностью байтов. Используйте 'memcpy' или что-то в этом роде. –

+0

О, вы абсолютно правы; n - «до». О, дерьмо. О, о, дерьмо. Я думаю, вы могли бы просто указать мою ошибку, которую я опубликовал по другому вопросу: http://stackoverflow.com/questions/4451977/data-corruption-wheres-the-bug –

1

подход я использовал (с Python 2.x) заключается в объявлении параметров типа строка в функции подписи, так что код Cython делает все преобразования и тип автоматической проверки:

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

Несколько комментариев к вашему коду, чтобы помочь улучшить его, ИМХО. Есть функции, предоставляемые API-интерфейсом python C, которые делают именно то, что вам нужно делать, и убедитесь, что все соответствует способу 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 внутренний char * буфер без копирования. Что бы ни изменяет ciphertext изменит retval. Поскольку retval - это новая Python-страница, ее можно изменить с помощью кода C до того, как она будет возвращена с _real_encrypt.

См. Документы Python C/API по вышеуказанным функциям для получения более подробной информации, here и here.

Чистый эффект экономит вам копию. Весь код будет выглядеть примерно так:

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 

 Смежные вопросы

  • Нет связанных вопросов^_^