Search code examples
pythonqtctypespyside2

python3 memoryview object to C function char* parameter


The frame is following: PySide2 QImage gives me access to the internal pixel data with the bits() - call. The call returns memoryview - object that i would like to pass C-library function that does processing on the image pixel data (say it does super-fancy-blur for an example).

Problem is that i have not found a way to get memoryview underlaying data as ctypes call parameter.

That is: how can i get from memoryview object ctypes.c_void_p (or similar) -- without making copy of the buffer. The memoryview tobytes() returns bytes, but its copy from the memory, and so modifying that will not modify the original image (pixel data).

Now the minimal example python silly.py side is:

import ctypes
from PySide2.QtGui import QImage

img = QImage(500,100, QImage.Format.Format_RGB32 )
data = img.bits()
print(data)

dll = ctypes.CDLL( "./silly.o" )
dll_set_image = getattr( dll, "set_image" )
dll_set_image.argtypes = [ ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32 ]
# Next line gives: ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
dll_set_image( data, img.width(), img.height() ) 

C-"dll": silly.c

#include <stdint.h>
#include <stdio.h>

void set_image( void* data, uint32_t xsize, uint32_t ysize )
{
   printf("Data set %lx %d %d", (uint64_t)data, xsize, ysize );
}

Compiling and testing:

% gcc -shared -o silly.o silly.c
% python3 silly.py              
<memory at 0x7f2f9806a700>
Traceback (most recent call last):
  File "silly.py", line 13, in <module>
    dll_set_image( data, img.width(), img.height() ) 
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type


Solution

  • A memoryview supports the Python buffer protocol, so create a ctypes array of the appropriate size using its from_buffer method, which won't make a copy but reference the same memory.

    Modifying your example slightly to see the buffer change:

    silly.c:

    #include <stdint.h>
    #include <stdio.h>
    
    #ifdef _WIN32
    #   define API __declspec(dllexport)
    #else
    #   define API
    #endif
    
    API void set_image(unsigned char* data, uint32_t xsize, uint32_t ysize)
    {
        printf("%p %u %u\n",data,xsize,ysize);
        *data = 123;
    }
    

    test.py:

    import ctypes
    from PySide2.QtGui import QImage
    
    img = QImage(500,100, QImage.Format.Format_RGB32 )
    data = img.bits()
    print('original:',data[0])
    
    dll = ctypes.CDLL('./silly')
    dll_set_image = dll.set_image
    dll_set_image.argtypes = ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32
    
    buffer = (ctypes.c_ubyte * len(data)).from_buffer(data)
    dll_set_image(buffer, img.width(), img.height())
    print('final:',data[0])
    

    Output:

    original: 80
    00000213CCF3C080 500 100
    final: 123