Python ctypes and memoryview


I’m trying to rewrite some old C code that I had made around 10 years ago in Python 3.
This involves interfacing with SDL 2 via the ctypes module. I am aware that PySDL2 exists and I have used it, but would like to do this this way so that I can understand how to interface with any shared library/DLL.

The definition of an SDL_Surface in C is detailed at [SDL_Surface - SDL Wiki'](libsdl wiki)
I have defined it in Python as:-

class SDL_Surface(ctypes.Structure):
    _fields_ = (('flags', ctypes.c_uint), \
                ('format', ctypes.c_void_p), \
                ('w', ctypes.c_int), \
                ('h', ctypes.c_int), \
                ('pitch', ctypes.c_int), \
                ('pixels', ctypes.POINTER(ctypes.c_ubyte)), \
                ('userdata', ctypes.c_void_p), \
                ('locked', ctypes.c_int), \
                ('lock_data', ctypes.c_void_p), \
                ('clip_rect', SDL_Rect), \
                ('map', ctypes.c_void_p), \
                ('refcount', ctypes.c_int))

Accessing the pixels via instancename.contents.pixels[i] works, but is slow. A selective memcpy-style routine on a surface with 32-bit pixels of width and height 256 is slow enough on my i7 that the frame timing code doesn’t work properly.

I would like to instead create instances of memoryview(instancename.contents.pixels) and try to make faster changes to pixels without creating copies with each operation, but this doesn’t work properly for two reasons:-

  1. The resulting memoryview instance is apparently only eight bytes in length, meaning it is likely looking at the pointer to the pixels instead of the pixels themselves;
  2. The format type of the memoryview is always ‘<B’. As I understand this, based on the documentation from the struct module, it equates to “little endian unsigned byte”. Endian-ness can only be applied to multi-byte integers, so to apply it to an array of singular bytes makes no sense. In addition, memoryview objects cannot operate on the format given and keep raising NotImplementedError: memoryview: unsupported format <B exceptions.

I can change the definition of the pixels field to ctypes.POINTER(ctypes.c_void_p), but how do I obtain the correct address and length to be able to make a mutable memoryview from it?

1 Like

Can you post an example of that? Which python are you on? I don’t know ctypes very well but maybe making pixel an array (constant size) instead of a POINTER may help? But what you really need is to convince Python it should make a buffer out of it… maybe some C glue is needed for that.

I’m using Python 3.7.7.
Looking on the Internet, there is apparently a problem between Python 2 and 3 where the implementation of memoryview changed, as a consequence it no longer makes assumptions about the layout of ctypes arrays.
Anyway, I think I have solved the issue, by changing the definition of the SDL_Surface pixels field to ctypes.c_void_p, and constructing the memoryview as memoryview((ctypes.c_ubyte * (instancename.contents.pitch * instancename.contents.h)).from_address(instancename.contents.pixels)).cast('B')
This improves performance a small amount, but I suppose doing a pixel-by-pixel copy in pure Python is never going to be particularly impressive…