将bytearray传递给C ++扩展时的TypeError

时间:2014-02-03 08:38:45

标签: python c++

Python代码:

image = urllib2.urlopen('http://localhost/test.png').read()
bytes = bytearray(image)
print [myext.do_stuff(bytes, mode=1)]

C ++代码:

static PyObject * 
do_stuff(PyObject *self, PyObject *args, PyObject *kwargs)
{
   PyByteArrayObject *imgdata;
   char *image;
   int mode;
   char *keywords[] = { "image", "mode", NULL };

   if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i", keywords, &imgdata, &mode))
      return NULL;

   image = PyByteArray_AsString((PyObject*) imgdata);
   char *result = do_something_more(image, mode);
   return Py_BuildValue("s", result);
}

添加了:

char * do_something_more(char imagebuffer[], int mode)
{
  vector<char> vec(imagebuffer, imagebuffer + sizeof(imagebuffer));
  Mat input = imdecode(vec, 1);
}

1 个答案:

答案 0 :(得分:2)

typeerror只是因为Y格式说明符在python2中存在但仅在python3中存在。如果要在python2中传递bytearray,则必须使用O格式说明符。

结果字符串只是实际事件的前几个字节这一事实非常简单:

  • strlen是一个处理C null终止字符串的C函数。您的图像数据包含一些空字节,因此该函数返回实际大小。
  • PyBuild_Values格式说明符采用C null终止字符串并返回python字符串对象。由于您的数据包含空字节,因此并非所有内容都放在结果中。

在您的C ++代码中,char *image指针 指向所有数据,但您依赖于C的字符串函数,如果您的字符串包含空字节。您必须始终跟踪字符串的长度。


更明确我的意思。这是一个独立的C扩展,可用于演示您的问题:

#include <Python.h>


static PyObject * 
do_stuff(PyObject *self, PyObject *args, PyObject *kwargs)
{
   PyByteArrayObject *imgdata;
   char *image;
   int mode;
   char *keywords[] = { "image", "mode", NULL };

   if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i", keywords, &imgdata, &mode))
      return NULL;

   image = PyByteArray_AsString((PyObject*) imgdata);
   return Py_BuildValue("s", image);
}

static PyMethodDef noddy_methods[] = {
    {"do_stuff",  do_stuff, METH_VARARGS | METH_KEYWORDS, "Does stuff"},
    {NULL, NULL, 0, NULL}  /* Sentinel */
};

void
initdemo(void) 
{
   (void) Py_InitModule("demo", noddy_methods);
}

使用setup.py

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo_ext.c'])

setup (name = 'Demo',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

用作:

>>> import demo
>>> with open('/Path/to/A/PNG/Image.png', 'rb') as f:
...     contents = f.read()
... 
>>> byt = bytearray(contents)
>>> byt[:20]
bytearray(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02V')
>>> demo.do_stuff(byt)   # "truncates" the data
'\x89PNG\r\n\x1a\n'

现在,如果您将do_stuff功能更改为:

static PyObject * 
do_stuff(PyObject *self, PyObject *args, PyObject *kwargs)
{
   PyObject *imgdata;
   char *image;
   int mode;
   Py_ssize_t length;
   char *keywords[] = { "image", "mode", NULL };

   if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i", keywords, &imgdata, &mode))
      return NULL;

   image = PyByteArray_AsString(imgdata);
   length = PyObject_Length(imgdata);
   PyObject *res = PyString_FromStringAndSize(image, length);
   return res;
}

你得到:

>>> import demo
>>> with open('/home/giacomo/Immagini/bad_grouping.png', 'rb') as f:
...     contents = f.read()
... 
>>> byt = bytearray(contents)
>>> byt[:20]
bytearray(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02V')
>>> demo.do_stuff(byt)[:20]
'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02V'

正如您所见,do_stuff 不再截断数据。

strlen之类的所有函数都假设字符串中没有空字节,并且当这不是真时(例如在这种情况下)将会出错。另外一些python的API调用假设C字符串,例如Py_BuildValue。您可以看到char *image 确实包含所有数据。问题是您正在使用无法正确处理它的函数。