Re: LP_c_ubyte buffer and length to python string?

Dan Stromberg <[email protected]> Sun, 24 Jul 2011 12:09:14 -0700
Newsgroups gmane.comp.python.ctypes
Message-ID <CAGGBd_qA+HWLEL7Wngz+1=widnoyqLWU-swoYjpUQw0V_+oSWA@mail.gmail.com>
On Sun, Jul 24, 2011 at 8:34 AM, Mads Kiilerich <[email protected]> wrote:

> Dan Stromberg wrote, On 07/23/2011 11:27 PM:
>
>> I've googled on this for hours, and found nothing that seems close to what
>> I want to do.
>>
>> I have a C function that accepts a preallocated buffer and a pointer to an
>> integer.  It then stashes data in the buffer, and passes back the buffer's
>> "in use" length in the integer.
>>
>> I'm creating the buffer with:
>>
>>      ctypes_compressed_buffer_char_**p = ctypes.create_string_buffer(**
>> maximum_size)
>>      ctypes_compressed_buffer = ctypes.cast(ctypes_compressed_**buffer_char_p,
>> ctypes.POINTER(ctypes.c_ubyte)**)
>>
>> Then I create my length with:
>>
>>      ctypes_compressed_size = ctypes.c_size_t(0)
>>      ctypes_compressed_size_pointer = ctypes.cast(ctypes.addressof(**ctypes_compressed_size),
>> ctypes.c_void_p)
>>
>> The length is actually a size_t, but passing a pointer to size_t in ctypes
>> was giving me type errors, hence the void *.
>>
>> Anyway, the function executes without reporting any errors, so I seem to
>> have my result - in a ctypes format.  Printing its type reports that its an
>> LP_c_ubyte.
>>
>> How can I convert the resulting LP_c_ubyte (array) and int to a Python 2.x
>> str and a 3.x bytes?
>>
>
> I assume you want to get the content of the chunk of memory that was
> allocated by create_string_buffer.


Yes.


> The simplest way to get that is
>  ctypes_compressed_buffer_char_p.raw[:length]
>

Doing this is at least giving me something of the correct length.

Besides that: It seems strange that you have to cast your buffer and your
> size_t.


It might have something to do with size_t being an alias.


> ctypes gives you all the power and danger of both Python and C at once, so
> just because it executes without reporting errors doesn't mean that you got
> it right.


Sure.  It's positively correlated with correct operation, but not perfectly
correlated.  Actually, years ago I wrote an article about the dangers of
languages that allow undefined memory references:
http://stromberg.dnsalias.org/~dstromberg/checking-early.html

Also, I "prototyped" in C (a touch ironic, yes) before getting into the
Python+ctypes version.  The C version is at
http://stromberg.dnsalias.org/svn/xz_mod/branches/ctypes/c-test

My unit test can now roundtrip compress+decompress, so I feel mostly good
about the module, though I of course still have some lingering questions
about memory leaks and undefined memory references.

Is it the case that ctypes variables are garbage collected, as long as the
functions you're calling don't do their own malloc'ing or other form of
memory allocation?

You might want to post a minimal self-contained code snippet that shows what
> function you are calling and how you are doing it.
>

I'm calling 3 functions:
1) One gets an upper bound on how big the compressed size of a block can be
2) The next compresses a block of data
3) The last uncompresses a block of data

I'm not sure how self-contained this is, but here's the class.  Note that I
really only care about in-memory compression for this project, as I'm only
compressing chunks of modest size, and I want to write them to disk later in
a way that skips the buffer cache using
http://stromberg.dnsalias.org/~dstromberg/odirect/ :

class Xz_ctypes:
    LZMA_OK                        = 0
    LZMA_TELL_NO_CHECK             = 1
    LZMA_CHECK_CRC32               = 1
    LZMA_NO_CHECK                  = 2
    LZMA_TELL_UNSUPPORTED_CHECK    = 2
    LZMA_UNSUPPORTED_CHECK         = 3
    LZMA_MEM_ERROR                 = 5
    LZMA_MEMLIMIT_ERROR            = 6
    LZMA_PRESET_DEFAULT            = 6
    LZMA_FORMAT_ERROR              = 7
    LZMA_OPTIONS_ERROR             = 8
    LZMA_DATA_ERROR                = 9
    LZMA_BUF_ERROR                 = 10
    LZMA_PROG_ERROR                = 11

    funcs = {}

    def __init__(self):
        pass

    @classmethod
    def declare_c_function(cls, library, name, argtypes=None, restype=None):
        '''Extract functions from liblzma'''
        try:
            func = getattr(cls.LZMA_LIB, '_%s' % name)
            cls.funcs[name] = func
        except AttributeError:
            func = getattr(cls.LZMA_LIB, name)
            cls.funcs[name] = func
        if argtypes is not None:
            func.argtypes = argtypes
        if restype is not None:
            func.restype = restype

    @classmethod
    def class_init(cls):
        cls.LZMA_LIBPATH = _find_lib('lzma')
        cls.C_LIBPATH = _find_lib('c')

        # *ix way - windows is different
        cls.LZMA_LIB = ctypes.CDLL(cls.LZMA_LIBPATH)
        cls.C_LIB = ctypes.CDLL(cls.LZMA_LIBPATH)

        cls.declare_c_function(cls.LZMA_LIB, 'lzma_stream_buffer_bound',
(ctypes.c_size_t, ), ctypes.c_size_t)
        #cls.declare_c_function(cls.C_LIB, 'malloc', (ctypes.c_size_t, ),
ctypes.c_void_p)
        #cls.declare_c_function(cls.C_LIB, 'free', (ctypes.c_void_p, ))

        # lzma_easy_buffer_encode notes:
        # Argument 1, uint32_t preset:
        #    Just a uint32 - simple
        # Argument 2, lzma_check check:
        #    An enum to the C programmer - that should usually be an
unsigned integer in the C runtime.
        # Argument 3, lzma_allocator *lzma_allocator:
        #    This is really a pointer to a struct, but happily, we only need
to pass NULL to it, so we can just treat it as a void *
        # Argument 4: uint8_t *in:
        # Argument 5: size_t in_size:
        # Argument 6: uint8_t *out
        # Argument 7: size_t *out_pos
        # Argument 8: size_t out_size
        #
        # The return type is also an enum, so probably an unsigned int
        c_size_t_p = ctypes.POINTER(ctypes.c_size_t)
        cls.declare_c_function(
            cls.LZMA_LIB,
            'lzma_easy_buffer_encode',
                (
                ctypes.c_uint32,                    # preset
                ctypes.c_uint,                      # check
                ctypes.c_void_p,                    # lzma_allocator
                ctypes.POINTER(ctypes.c_uint8),     # uint8_t *in
                ctypes.c_size_t,                    # size_t in_size
                ctypes.POINTER(ctypes.c_uint8),     # uint8_t *out
                ctypes.c_void_p,                    # size_t *out_pos #
ctypes made size_t * think it was a long, so we use void_p
                ctypes.c_size_t,                    # size_t out_size
                ),
            ctypes.c_uint,
            )

        cls.declare_c_function(
            cls.LZMA_LIB,
            'lzma_stream_buffer_decode',
                (
                ctypes.POINTER(ctypes.c_uint64),    # uint64_t *memlimit
                ctypes.c_uint32,                    # uint32_t flags
                ctypes.c_void_p,                    # lzma_allocator
*allocator,
                ctypes.POINTER(ctypes.c_uint8),     # const uint8_t *in
                ctypes.c_void_p,                    # size_t *in_pos
                ctypes.c_size_t,                    # size_t in_size,
                ctypes.POINTER(ctypes.c_uint8),     # uint8_t *out
                ctypes.c_void_p,                    # size_t *out_pos
                ctypes.c_size_t,                    # size_t out_size
                ),
            ctypes.c_uint,
            )

    @classmethod
    def get_xz_error(cls, ret_xz):
        '''Decode an lzma_ret enum to an at-least-somewhat-descriptive
string'''
        if ret_xz == cls.LZMA_PRESET_DEFAULT:
            return 'LZMA_PRESET_DEFAULT'
        elif ret_xz == cls.LZMA_CHECK_CRC32:
            return 'LZMA_CHECK_CRC32'
        elif ret_xz == cls.LZMA_OK:
            return 'LZMA_OK'
        elif ret_xz == cls.LZMA_FORMAT_ERROR:
            return 'LZMA_FORMAT_ERROR'
        elif ret_xz == cls.LZMA_OPTIONS_ERROR:
            return 'LZMA_OPTIONS_ERROR'
        elif ret_xz == cls.LZMA_DATA_ERROR:
            return 'LZMA_DATA_ERROR'
        elif ret_xz == cls.LZMA_NO_CHECK:
            return 'LZMA_NO_CHECK'
        elif ret_xz == cls.LZMA_TELL_NO_CHECK:
            return 'LZMA_TELL_NO_CHECK'
        elif ret_xz == cls.LZMA_UNSUPPORTED_CHECK:
            return 'LZMA_UNSUPPORTED_CHECK'
        elif ret_xz == cls.LZMA_TELL_UNSUPPORTED_CHECK:
            return 'LZMA_TELL_UNSUPPORTED_CHECK'
        elif ret_xz == cls.LZMA_MEM_ERROR:
            return 'LZMA_MEM_ERROR'
        elif ret_xz == cls.LZMA_MEMLIMIT_ERROR:
            return 'LZMA_MEMLIMIT_ERROR: Memory usage limit was reached.
minimum required memlimit value was stored to *memlimit'
        elif ret_xz == cls.LZMA_BUF_ERROR:
            return 'LZMA_BUF_ERROR: Output buffer was too small'
        elif ret_xz == cls.LZMA_PROG_ERROR:
            return 'LZMA_PROG_ERROR'
        else:
            return 'Unrecognized lzma_ret value: %d' % ret_xz

    @classmethod
    def compress(cls, input_data):
        '''Compress data into xz format using ctypes to access liblzma.so'''
        # maximum_size = lzma_stream_buffer_bound(input_buffer_size);
        length_input_data = len(input_data)
        maximum_size =
cls.funcs['lzma_stream_buffer_bound'](length_input_data)

        # This is an efficient way of creating a readonly ctypes string
        ctypes_input_data_char_p = ctypes.c_char_p(input_data)
        ctypes_input_data = ctypes.cast(ctypes_input_data_char_p,
ctypes.POINTER(ctypes.c_ubyte))

        # This is a less-fast but mutable way of creating a ctypes string
        ctypes_compressed_buffer_char_p =
ctypes.create_string_buffer(maximum_size)
        ctypes_compressed_buffer =
ctypes.cast(ctypes_compressed_buffer_char_p, ctypes.POINTER(ctypes.c_ubyte))

        ctypes_compressed_size = ctypes.c_size_t(0)
        #ctypes_compressed_size_pointer = ctypes.POINTER(ctypes.c_size_t)
        ctypes_compressed_size_pointer =
ctypes.cast(ctypes.addressof(ctypes_compressed_size), ctypes.c_void_p)

        # lzma_easy_buffer_encode
        ret_xz = cls.funcs['lzma_easy_buffer_encode'](
          cls.LZMA_PRESET_DEFAULT,
            cls.LZMA_CHECK_CRC32,
            None,
            ctypes_input_data,
            length_input_data,
            ctypes_compressed_buffer,
            ctypes_compressed_size_pointer,
            maximum_size,
            )

        if ret_xz != cls.LZMA_OK:
            raise OSError(cls.get_xz_error(ret_xz))

        resultant_length = int(ctypes_compressed_size.value)
        result = ctypes_compressed_buffer_char_p.raw[:resultant_length]

        return result

    @classmethod
    def decompress(cls, input_data, max_result_size = 2 ** 24):
        '''Uncompress data from xz format using ctypes to access
liblzma.so'''
        ctypes_memlimit = ctypes.c_uint64(max_result_size)
        ctypes_memlimit_pointer =
ctypes.cast(ctypes.addressof(ctypes_memlimit),
ctypes.POINTER(ctypes.c_uint64))

        ctypes_input_data_char_p = ctypes.c_char_p(input_data)
        ctypes_input_data = ctypes.cast(ctypes_input_data_char_p,
ctypes.POINTER(ctypes.c_ubyte))

        ctypes_in_pos = ctypes.c_size_t(0)
        ctypes_in_pos_pointer = ctypes.cast(ctypes.addressof(ctypes_in_pos),
ctypes.c_void_p)

        in_size = ctypes.c_size_t(len(input_data))

        ctypes_uncompressed_buffer_char_p =
ctypes.create_string_buffer(max_result_size)
        ctypes_uncompressed_buffer =
ctypes.cast(ctypes_uncompressed_buffer_char_p,
ctypes.POINTER(ctypes.c_ubyte))

        ctypes_out_pos = ctypes.c_size_t(0)
        ctypes_out_pos_pointer =
ctypes.cast(ctypes.addressof(ctypes_out_pos), ctypes.c_void_p)

        out_size = ctypes.c_size_t(max_result_size)

        ret_xz = cls.funcs['lzma_stream_buffer_decode'](
            ctypes_memlimit_pointer,            # uint64_t *memlimit
            cls.LZMA_TELL_NO_CHECK,             # uint32_t flags
            None,                               # lzma_allocator *allocator,
            ctypes_input_data,                  # const uint8_t *in
            ctypes_in_pos_pointer,              # size_t *in_pos
            in_size,                            # size_t in_size,
            ctypes_uncompressed_buffer,         # uint8_t *out
            ctypes_out_pos_pointer,             # size_t *out_pos
            out_size,                           # size_t out_size
            )

        if ret_xz != cls.LZMA_OK:
            raise OSError(cls.get_xz_error(ret_xz))

        resultant_length = int(ctypes_out_pos.value)
        result = ctypes_uncompressed_buffer_char_p.raw[:resultant_length]

        return result

if HAVE_CTYPES:
    XZ_CTYPES = Xz_ctypes()
    XZ_CTYPES.class_init()

------------------------------------------------------------------------------
Magic Quadrant for Content-Aware Data Loss Prevention
Research study explores the data loss prevention market. Includes in-depth
analysis on the changes within the DLP market, and the criteria used to
evaluate the strengths and weaknesses of these DLP solutions.
http://www.accelacomm.com/jaw/sfnl/114/51385063/

_______________________________________________
ctypes-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ctypes-users