Cython到python和具有未知大小字符数组的c库之间的接口

Cython到python和具有未知大小字符数组的c库之间的接口,python,memory,dynamic,cython,cythonize,Python,Memory,Dynamic,Cython,Cythonize,我有一个C库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大字符*中,以将数据返回给任何调用它的对象。这在C语言中运行良好,但使用python/Cython时,我在分配内存时遇到了问题 图书馆原型是: int readWrapper(结构选项opt,char*lineOut) 我的pyx文件我有: from libc.string cimport strcpy, memset from libc.stdlib cimport malloc, free from libc.stdio

我有一个C库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大字符*中,以将数据返回给任何调用它的对象。这在C语言中运行良好,但使用python/Cython时,我在分配内存时遇到了问题

图书馆原型是:
int readWrapper(结构选项opt,char*lineOut)

我的pyx文件我有:

from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf

cdef extern from "reader.h":
    struct options:
        int debug;
        char *filename; 

    options opt
    int readWrapper(options opt, char *lineOut);

def pyreader(file, date, debug=0):
    import logging
    cdef options options
    
    # Get the filename
    options.filename = <char *>malloc(len(file) * sizeof(char)) 
    options.debug = debug
    # Size of array
    outSize = 50000

    cdef char *line_output = <char *> malloc(outSize * sizeof(char))
    memset(line_output, 1, outSize)
    line_output[outSize] = 0

   # Call reader
   return_val = readWrapper(options, line_output)

   # Create dataframe
   from io import StringIO
   data = StringIO(line_output.decode('UTF-8', 'strict'))
   df = pd.read_csv(data, delim_whitespace=True, header=None)
   # Free memory
   free(line_output)
   return df 
新的pyx文件:

from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf

cdef extern from "reader.h":
    struct options:
        int debug;
        char *filename; 

    options opt
    int readWrapper(options opt, char *lineOut);

def pyreader(file, date, debug=0):
    import logging
    cdef options options
    
    # Get the filename
    options.filename = <char *>malloc(len(file) * sizeof(char)) 
    options.debug = debug

    cdef char *line_output = NULL

    # Call reader
    return_val = readWrapper(options, &line_output)

    # Create dataframe
    from io import StringIO
    data = StringIO(line_output.decode('UTF-8', 'strict'))
    df = pd.read_csv(data, delim_whitespace=True, header=None)
    
    # Free memory
    free(line_output)
    free(options.filename)
    return df 
使用
python3 test.py | head时
。如果没有头部,则不会显示错误

最后,关于文件名及其分配的建议也不适用于我。使用
options.filename=file
会导致
TypeError:expected bytes,str在运行时找到
,但会编译。有趣的是,只有在运行调用包装器的python代码时才会发生这种情况:
python3 test.py | head
。如果没有管道和封头,则不存在断开管道错误。因此,这没什么大不了的,但我想知道是什么导致了它

搜索BrokenPipeError后编辑 此断开管道错误问题发生在头部,而不是尾部。可在此处找到此“错误”的解释:

解决方案pyx文件: 与前面提到的readWrapper.c一起使用的最后一个reader.pyx文件。内存分配由C处理,清理(最后)由pyx代码处理

from libc.stdlib cimport free

cdef extern from "reader.h":
    struct options:
        int debug;
        char *filename; 
        char *DAY;

    options opt
    int readWrapper(options opt, char **lineOut);

def pyreader(file, date, debug=0):
    import logging
    import sys
    import errno
    import pandas as pd
    # Init return valus
    a = pd.DataFrame()
    cdef options options
    cdef char *line_output = NULL

    # logging
    logging.basicConfig(stream=sys.stdout, 
                        format='%(asctime)s:%(process)d:%(filename)s:%(lineno)s:pyreader: %(message)s',
                        datefmt='%Y%m%d_%H.%M.%S',
                        level=logging.DEBUG if debug > 0 else logging.INFO)
    
    try:        
        # Check inputs
        if file is None:
            raise Exception("No valid filename provided")
        if date is None:
            raise Exception("No valid date provided")

        # Get the filename
        file_enc = file.encode("ascii")
        options.filename = file_enc
        # Get date
        day_enc = date.encode('ascii')
        options.DAY = day_enc
        
        try:
            # Call reader
            return_val = readWrapper(options, &line_output)

            if (return_val > 0):
                logging.error("pyreadASTERIX2 failed with exitcode {}".format(return_val))
                return a
        except Exception:
            logging.exception("Error occurred")
            free(line_output)
            return a

        from io import StringIO
        try:
            data = StringIO(line_output.decode('UTF-8', 'strict'))
            logging.debug("return_val: {} and size: {}".format(return_val, len(line_output.decode('UTF-8', 'strict'))))


            a = pd.read_csv(data, delim_whitespace=True, header=None, dtype={'id':str})
            if a.empty:
                logging.error("failed to load {} not enough data to construct DataFrame".format(file))
                return a 
            logging.debug("converted data into pd")
        except Exception as e:
            logging.exception("Exception occured while loading: {} into DataFrame".format(file))
            return a
        finally:
            free(line_output)
        
        logging.debug("Size of df: {}".format(len(a)))
        # Success, return DataFrame
        return  a
    except Exception:
        logging.exception("pyreader returned with an exception:")
        return a

您有两个基本选项:

  • 事先弄清楚如何计算尺寸

     size = calculateSize(...)  # for example, by pre-reading the file
     line_output = <char*>malloc(size)
     return_val = readWrapper(options, line_output)
    
    b。将指针传递给指针并更改它

    // C 
    int readWrapper(options opt, char** str_out) {
        // work out the length
        *str_out = malloc(length);
        // etc
    }
    
    # Cython
    char* line_out
    return_value = readWrapper(options, &line_out)
    

  • 您需要确保您分配的所有字符串都已清理。您仍然存在
    选项.filename的内存泄漏。对于
    options.filename
    您最好通过Cython获取指向
    文件内容的指针。只要
    文件
    存在,这是有效的,因此您不需要进行分配

    options.filename = file
    
    只需确保
    选项
    不会超过
    文件
    (也就是说,它不会被存储起来供以后在C中的任何地方使用)

    大体上

    something = malloc(...)
    try:
        # code
    finally:
        free(something)
    

    是确保清理的良好模式。

    行输出[outSize]=0
    -这是最后一个。此外,还存在一些内存泄漏。不过,最根本的问题是,您的C库有一个完全损坏的接口—它在不知道数组大小的情况下写入C数组。要想修好它,就必须扔掉这个图书馆。幸运的是,我控制了这个图书馆。该库还由一个可执行文件使用,该文件将数据打印到标准输出。我还希望在pandas数据帧中提供相同的数据,因此使用这个pyx例程以及这个char数组和StringIO。由于文件的二进制性质,数据帧中要打印或存储的数据量不可能事先知道。你知道如何绕过这个或使用这个字符*?解包数据是一行,每行包含20到25个“列”(取决于选项)。我将让
    readWrapper
    负责分配内存。要么返回数组,要么使用指针到指针的参数。我选择了选项2b,并尝试了一些方法。不幸的是,readWrapper是专有的,我不允许在这里共享它,但只要我删除所有printf语句,它就可以工作。fprintf(stderr,“string here”)可以很好地工作,但使用stdout时,我会遇到我真的不理解的断管错误。关于剩余的漏洞,我也释放了这些漏洞,因此将编辑我的OP。使用您的建议会导致:TypeError:expected bytes,str found,我也不理解。谢谢你!我猜编码错误在
    options.filename=file
    ?这是因为Python3字符串是Unicode的,所以不要简单地转换为C字符串。您可能需要
    file\u enc=file.encode(“ascii”);options.filename=file_enc
    。显然ascii可能不是正确的编码。。。我恐怕对
    BrokenPipeErrors
    一点线索也没有。您是否实际设置了打开的文件名?我想知道这是否是您的一些问题的根源……嗨,大卫,我将尝试编码并报告。还添加了关于管道错误的新注释,该错误已“解决”,但仍不理解该问题。关于该文件,我的问题现在包含最新版本,包括打开该文件的readWrapper函数。我发现管道问题是头部如何工作的结果(通过“过早”关闭管道,例如,尾部不会折断管道)。请看下面的答案:
    // C 
    int readWrapper(options opt, char** str_out) {
        // work out the length
        *str_out = malloc(length);
        // etc
    }
    
    # Cython
    char* line_out
    return_value = readWrapper(options, &line_out)
    
    options.filename = file
    
    something = malloc(...)
    try:
        # code
    finally:
        free(something)