Hi again,
On Mon, 2009-03-23 at 14:36 +0100, Jens Rantil wrote:
> So I have a C-function in a DLL loaded through ctypes. This particular
> function returns a pointer to a double. In fact I know that this
> pointer points to the first element in an array of, say for
> simplicity, 200 elements.
>
> How do I convert this pointer to a NumPy array that uses this data
> (ie. no copy of data in memory)? I am able to create a numpy array
> using a copy of the data.
Just a follow up on this topic:
While constructing a home made __array_interface__ attribute does the
job of converting from a ctypes pointer to a NumPy array, it seems both
like an undocumented and magic solution to a common problem.
Therefor, I used the code that Sturla Molden posted and wrote a highly
useful piece of code that enables ctypes DLL functions to return NumPy
arrays on the fly. See it as a replacement for the incorrectly
documented ndpointer/restype functionality if you want. The code is
attached with the mail, including Nose tests.
An example workflow to have a ctypes DLL function return a 4 element
double array would be:
>>> returns_ndarray(dll.my_func, ctypes.c_double, 4)
>>> my_array = dll.my_func()
>>> my_array
array([ 2.1, 4. , 97. , 6. ])
Notice that 'my_array' will be sharing the memory that the DLL function
returned. Also, I have not done extensive testing of ndim and shape
parameters.
Wouldn't my code, or a tweak of it, be a nice feature in
numpy.ctypeslib? Is this the wrong channel for proposing things like
this?
Thanks,
Jens Rantil
Lund University & Modelon AB, Sweden
import ctypes
import numpy as N
import nose
def _from_address(address, nbytes, dtype):
"""Converts a C-array to a numpy.array.
Credits to Sturla Molden:
http://mail.scipy.org/pipermail/numpy-discussion/2009-March/041323.html
"""
class Dummy(object): pass
d = Dummy()
bytetype = N.dtype(N.uint8)
d.__array_interface__ = {
'data' : (address, False),
'typestr' : bytetype.str,
'descr' : bytetype.descr,
'shape' : (nbytes,),
'strides' : None,
'version' : 3
}
return N.asarray(d).view(dtype)
class _PointerToNDArrayConverter:
"""A callable class used by the function _returns_ndarray(...)
to convert result from a DLL function pointer to an array.
"""
def __init__(self, shape, dtype, ndim=1, order=None):
"""Set meta data about the array the returned pointer is
pointing to.
@param shape:
A tuple containing the shape of the array
@param dtype:
The data type that the function result points to.
@param ndim:
The optional number of dimensions that the result
returns.
@param order (optional):
Optional. The same order parameter as can be used in
numpy.array(...).
"""
assert ndim >= 1
self._shape = shape
self._dtype = dtype
self._order = order
if ndim is 1:
self._num_elmnts = shape
try:
# If shape is specified as a tuple
self._num_elmnts = shape[0]
except TypeError:
pass
else:
assert len(shape) is ndim
for number in shape:
assert number >= 1
self._num_elmnts = reduce(lambda x,y: x*y, self.shape)
def __call__(self, ret, func, params):
if ret is None:
raise JMIException("The function returned NULL.")
#ctypes_arr_type = C.POINTER(self._num_elmnts * self._dtype)
#ctypes_arr = ctypes_arr_type(ret)
#narray = N.asarray(ctypes_arr)
pointer = ctypes.cast(ret, ctypes.c_void_p)
address = pointer.value
nbytes = ctypes.sizeof(self._dtype) * self._num_elmnts
numpy_arr = _from_address(address, nbytes, self._dtype)
return numpy_arr
def returns_ndarray(dll_func, dtype, shape, ndim=1, order=None):
"""Helper function to set automatic conversion of DLL function
result to a NumPy ndarray.
"""
# Defining conversion function (actually a callable class)
conv_function = _PointerToNDArrayConverter(shape=shape, \
dtype=dtype, \
ndim=ndim, \
order=order)
dll_func.restype = ctypes.POINTER(dtype)
dll_func.errcheck = conv_function
class testReturnsNDArray():
"""Tests the (private) function _returns_ndarray(...)
"""
def testDoubleType(self):
"""Test the function using the double data type.
"""
# The function to test
returns_ndarray = _from_address
ctypes_arr = (4 * ctypes.c_double)(1.2, 1.8, 5.4, 8.32)
address = ctypes.addressof(ctypes_arr)
narray = returns_ndarray(address, ctypes.sizeof(ctypes_arr) \
* ctypes.sizeof(ctypes.c_double),
ctypes.c_double)
nose.tools.assert_equal(ctypes_arr[0], narray[0])
nose.tools.assert_equal(ctypes_arr[3], narray[3])
ctypes_arr[0] = 3.78
nose.tools.assert_equal(ctypes_arr[0], narray[0])
ctypes_arr[3] = 14.79
nose.tools.assert_equal(ctypes_arr[3], narray[3])
narray[0] = 3.42
nose.tools.assert_equal(ctypes_arr[0], narray[0])
narray[3] = 9.17
nose.tools.assert_equal(ctypes_arr[3], narray[3])
def testIntType(self):
"""Test the function using the int data type.
"""
# The function to test
returns_ndarray = _from_address
ctypes_arr = (4 * ctypes.c_int)(2, 8, 5, 3)
address = ctypes.addressof(ctypes_arr)
narray = returns_ndarray(address, ctypes.sizeof(ctypes_arr) \
* ctypes.sizeof(ctypes.c_int),
ctypes.c_int)
nose.tools.assert_equal(ctypes_arr[0], narray[0])
nose.tools.assert_equal(ctypes_arr[3], narray[3])
ctypes_arr[0] = 78
nose.tools.assert_equal(ctypes_arr[0], narray[0])
ctypes_arr[3] = 179
nose.tools.assert_equal(ctypes_arr[3], narray[3])
narray[0] = 3427
nose.tools.assert_equal(ctypes_arr[0], narray[0])
narray[3] = 917
nose.tools.assert_equal(ctypes_arr[3], narray[3])
_______________________________________________
Numpy-discussion mailing list
Numpy-discussion@scipy.org
http://mail.scipy.org/mailman/listinfo/numpy-discussion