working with ctypes and complex data structures

eryk sun eryksun at gmail.com
Wed Oct 5 18:15:35 EDT 2016


On Wed, Oct 5, 2016 at 9:03 PM, Michael Felt <michael at felt.demon.nl> wrote:
>
>> +80          args = (1, "name", None), (2, "buff", None), (1, "size",
>> 0), (1, "count", 1)
>
> error #1. paramater type 2 (the buffer might be where data is being put, but
> for the call, the pointer is INPUT)

An output parameter (type 2) has ctypes implicitly create the buffer
and insert it in the argument list. It gets returned as the call's
result. Here's a contrived example:

    class Buf(ctypes.Structure):
        _fields_ = (('value', ctypes.c_char * 100),)

    LP_Buf = ctypes.POINTER(Buf)

    proto = ctypes.CFUNCTYPE(ctypes.c_int,
                LP_Buf, ctypes.c_char_p, ctypes.c_int)
    flags = ((2, 'buf'), (1, 'fmt'), (1, 'arg', 42))
    sprintf = proto(('sprintf', ctypes.CDLL(None)), flags)

    >>> r = sprintf(b'The answer is %d.')
    >>> r.value
    b'The answer is 42.'

This works best when combined with an errcheck function. This lets you
see the actual arguments that were passed in the call and the original
result:

    def errcheck(result, func, args):
        print('result:', result)
        print('args:', args)
        return args

    sprintf.errcheck = errcheck

    >>> r = sprintf(b'The answer is %d.')
    result: 17
    args: (<__main__.Buf object at 0x7fede840bb70>, b'The answer is %d.', 42)

When the errcheck function returns "args", this tells ctypes to
continue with its normal post-processing, so instead of the actual 17
result, it returns the "buf" output parameter:

    >>> r.value
    b'The answer is 42.'

Notice that I declared the output parameter's type as a pointer type,
but ctypes is smart enough to create a Buf instance for me instead of
just a pointer. Also, because the argument type is a pointer type, its
from_param method implicitly passes the Buf instance by reference.

> class perfstat_xxx:
>     def init(self):
>         # AIX member in an archive
>         _perflib = ctypes.CDLL("libperfstat.a(shr_64.o)")
>         _fn_xxx = _perflib.xxx
>         # ALL AIX perfstat routines have 4 arguments:
>         # (name, buff, sizeof_buff, count)
>         # the buff is a pointer to where the information will be stored
>         _fn = CFUNCTYPE(c_int, c_void_p, POINTER(xxx_t), c_int, c_int)
>         _args = ((1, "name", None), (1, "buff", None), (1, "size", 0),
>                  (1, "count", 1))
>         _xxx = _fn(("xxx", _fn_xxx), _args)

You should really do this work at the class or module level. Every
time this init() method is called, you're needlessly incrementing the
reference count on the "libperfstat.a(shr_64.o)" shared library and
needlessly creating and instantiating the function pointer type.



More information about the Python-list mailing list