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

Reply via email to