NumPy frombuffer giving nonsense values when reading C float array on Windows

eryk sun eryksun at gmail.com
Tue Jul 26 14:09:47 EDT 2016


 On Tue, Jul 26, 2016 at 12:06 PM,  <urschrei at gmail.com> wrote:
> I'm using ctypes to interface with a binary which returns a void pointer (ctypes c_void_p) to a nested 64-bit float array:

If this comes from a function result, are you certain that its restype
is ctypes.c_void_p? I commonly see typos here such as setting
"restypes" instead of "restype".

> [[1.0, 2.0], [3.0, 4.0], … ]
> then return the pointer so it can be freed
>
> I'm using the following code to de-reference it:
>
> # a 10-element array
> shape = (10, 2)
> array_size = np.prod(shape)
> mem_size = 8 * array_size
> array_str = ctypes.string_at(ptr, mem_size)
> # convert to NumPy array,and copy to a list
> ls = np.frombuffer(array_str, dtype="float64", count=array_size).reshape(shape).tolist()
> # return pointer so it can be freed
> drop_array(ptr)
> return ls
>
> This works correctly and consistently on Linux and OSX using NumPy 1.11.0, but fails on
> Windows 32 bit and 64-bit about 50% of the time, returning nonsense values. Am I doing
> something wrong? Is there a better way to do this?

numpy.ctypeslib facilitates working with ctypes functions, pointers
and arrays via the factory functions as_array, as_ctypes, and
ndpointer.

ndpointer creates a c_void_p subclass that overrides the default
from_param method to allow passing arrays as arguments to ctypes
functions and also implements the _check_retval_ hook to automatically
convert a pointer result to a numpy array.

The from_param method validates an array argument to ensure it has the
proper data type, shape, and memory layout. For example:

    g = ctypes.CDLL(None) # Unix only
    Base = np.ctypeslib.ndpointer(dtype='B', shape=(4,))

    # strchr example
    g.strchr.argtypes = (Base, ctypes.c_char)
    g.strchr.restype = ctypes.c_char_p

    d = np.array(list(b'012\0'), dtype='B')
    e = np.array(list(b'0123\0'), dtype='B') # wrong shape

    >>> g.strchr(d, b'0'[0])
    b'012'
    >>> g.strchr(e, b'0'[0])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ctypes.ArgumentError: argument 1: <class 'TypeError'>:
    array must have shape (4,)

The _check_retval_ hook of an ndpointer calls numpy.array on the
result of a function. Its __array_interface__ property is used to
create a copy with the defined data type and shape. For example:

    g.strchr.restype = Base

    >>> d.ctypes._as_parameter_ # source address
    c_void_p(24657952)
    >>> a = g.strchr(d, b'0'[0])
    >>> a
    array([48, 49, 50,  0], dtype=uint8)
    >>> a.ctypes._as_parameter_ # it's a copy
    c_void_p(19303504)

As a copy, the array owns its data:

    >>> a.flags
      C_CONTIGUOUS : True
      F_CONTIGUOUS : True
      OWNDATA : True
      WRITEABLE : True
      ALIGNED : True
      UPDATEIFCOPY : False

You can subclass the ndpointer type to have _check_retval_ instead
return a view of the result (i.e. copy=False), which may be desirable
for a large result array but probably isn't worth it for small arrays.
For example:

    class Result(Base):
        @classmethod
        def _check_retval_(cls, result):
            return np.array(result, copy=False)

    g.strchr.restype = Result

    >>> a = g.strchr(d, b'0'[0])
    >>> a.ctypes._as_parameter_ # it's NOT a copy
    c_void_p(24657952)

Because it's not a copy, the array view doesn't own the data, but note
that it's not a read-only view:

    >>> a.flags
      C_CONTIGUOUS : True
      F_CONTIGUOUS : True
      OWNDATA : False
      WRITEABLE : True
      ALIGNED : True
      UPDATEIFCOPY : False



More information about the Python-list mailing list