python3 memoryview 对象到 C 函数 char* 参数

python3 memoryview object to C function char* parameter

框架如下:PySide2 QImage 让我可以通过 bits() - 调用访问内部像素数据。调用 returns memoryview - 我想传递对图像像素数据进行处理的 C 库函数的对象(举个例子,假设它执行超级花式模糊)。

问题是我还没有找到一种方法来获取 memoryview 底层数据作为 ctypes 调用参数。

即:我如何从 memoryview 对象 ctypes.c_void_p(或类似对象)中获取——而不复制缓冲区。 memoryview tobytes() returns 字节,但是它是从内存中复制过来的,所以修改不会修改原图(像素数据)。

现在最小的例子python silly.py边是:

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 );
}

编译和测试:

% 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

memoryview is not a void * but a structure (Py_buffer) 其成员内部是缓冲区,因此转换不是直接的。

在这些情况下,我更喜欢使用 Cython(python -m pip install cython),它允许以简单的方式为库创建包装器。考虑到以上情况,我创建了一个项目:

├── main.py
├── psilly.c
├── psilly.pyx
├── setup.py
├── silly.c
├── silly.h
└── silly.pxd

silly.h

extern void set_image( unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels);

silly.c

#include <stdint.h>

#include "silly.h"

void set_image( unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels)
{
    // fill white color
    for (uint32_t i = 0; i < (xsize * ysize * channels); i++)
    {
        data[i] = 255;
    }
}

silly.pxd

from libc.stdint cimport uint32_t

cdef extern from "silly.h":  
    void set_image(unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels)

psilly.pyx

cimport silly

cpdef set_image(unsigned char[:] data, int width, int height, int channels):
    silly.set_image(&data[0], width, height, channels)

setup.py

from distutils.core import Extension, setup
from Cython.Build import cythonize

ext = Extension(
    name="psilly",
    sources=["psilly.pyx", "silly.c"],
)
setup(ext_modules=cythonize(ext))

main.py

import psilly

from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
from PySide2.QtGui import QImage, QPixmap

app = QApplication()

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

label1 = QLabel()
label1.setPixmap(QPixmap.fromImage(img.copy()))

psilly.set_image(data, img.width(), img.height(), 4)

label2 = QLabel()
label2.setPixmap(QPixmap.fromImage(img))

widget = QWidget()
lay = QHBoxLayout(widget)
lay.addWidget(label1)
lay.addWidget(label2)
widget.show()

app.exec_()

编译和测试:

python setup.py build_ext --inplace
python main.py

输出:

注意:编译项目时,它是作为psilly.c的中间元素生成的,你会看到memoryview和unsigned char *之间使用Py_buffer结构的转换。

A memoryview 支持 Python 缓冲区协议,因此使用其 from_buffer 方法创建一个适当大小的 ctypes 数组,该方法不会进行复制但引用相同的内存。

稍微修改您的示例以查看缓冲区变化:

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])

输出:

original: 80
00000213CCF3C080 500 100
final: 123

为了完整起见,人们从 google 中找到此页面,但另一种可行的替代方法是根据 docs 中所述使用 C/C++ 制作 python 模块.

它需要一些样板,但实际访问缓冲区很容易:

PyObject* set_image(PyObject *self, PyObject *args) 
{
    Py_buffer buffer;
    long xsize;
    long ysize;
    int ok = PyArg_ParseTuple(args, "y*ll", &buffer, &xsize, &ysize); 

    if ( ok != 1 )
        return NULL;

    printf("Data set: %d %d - %lx\n", (int)xsize, (int)ysize, (unsigned long)buffer.buf );

    Py_RETURN_NONE;
}