Python2和Python3的差异很大,这是为什么很多人都不愿意升级的原因。如果你用C/C++为Python2写过扩展模块,那么直接用Python3来编译是通不过的。这篇文章分享下如何编写兼容的C/C++代码。
环境搭建
OpenCV 3.3.0
pip install opencv-python
Numpy 1.11.2
pip install numpy
Visual Studio 2015
SET VS90COMNTOOLS=%VS140COMNTOOLS%
你需要的C/C++库。我这里用的是Dynamsoft Barcode Reader 5.2 for Windows。你需要把DLL拷贝到Python35\Lib\site-packages目录下。
模块初始化
Python官方有一篇文章Porting Extension Modules to Python 3,详细介绍了Python2和Python3的差异。阅读完文章你就可以写出最简单的C/C++兼容代码。
#include "Python.h"
struct module_state {
PyObject *error;
};
#if PY_MAJOR_VERSION >= 3
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
static struct module_state _state;
#endif
static PyObject *
error_out(PyObject *m) {
struct module_state *st = GETSTATE(m);
PyErr_SetString(st->error, "something bad happened");
return NULL;
}
static PyMethodDef myextension_methods[] = {
{"error_out", (PyCFunction)error_out, METH_NOARGS, NULL},
{NULL, NULL}
};
#if PY_MAJOR_VERSION >= 3
static int myextension_traverse(PyObject *m, visitproc visit, void *arg) {
Py_VISIT(GETSTATE(m)->error);
return 0;
}
static int myextension_clear(PyObject *m) {
Py_CLEAR(GETSTATE(m)->error);
return 0;
}
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"myextension",
NULL,
sizeof(struct module_state),
myextension_methods,
NULL,
myextension_traverse,
myextension_clear,
NULL
};
#define INITERROR return NULL
PyMODINIT_FUNC
PyInit_myextension(void)
#else
#define INITERROR return
void
initmyextension(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
PyObject *module = PyModule_Create(&moduledef);
#else
PyObject *module = Py_InitModule("myextension", myextension_methods);
#endif
if (module == NULL)
INITERROR;
struct module_state *st = GETSTATE(module);
st->error = PyErr_NewException("myextension.Error", NULL, NULL);
if (st->error == NULL) {
Py_DECREF(module);
INITERROR;
}
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
Python3接口的使用
这里分享下我碰到的接口变化问题。
String
Python2用的接口是PyString_FromString,而Python3用的是PyUnicode_FromFormat。
#if defined(IS_PY3K)
result = PyUnicode_FromFormat("%s", tmp->pBarcodeData);
#else
result = PyString_FromString(tmp->pBarcodeData);
#endif
NumPy相关数据获取:buffer, width, height, stride
这块部分网上的信息比较少,我通过源码找到了方法。
#if defined(IS_PY3K)
//Refer to numpy/core/src/multiarray/ctors.c
Py_buffer *view;
int nd;
PyObject *memoryview = PyMemoryView_FromObject(o);
if (memoryview == NULL) {
PyErr_Clear();
return -1;
}
view = PyMemoryView_GET_BUFFER(memoryview);
char *buffer = (char*)view->buf;
nd = view->ndim;
int len = view->len;
int stride = view->strides[0];
int width = view->strides[0] / view->strides[1];
int height = len / stride;
#else
PyObject *ao = PyObject_GetAttrString(o, "__array_struct__");
if ((ao == NULL) || !PyCObject_Check(ao)) {
PyErr_SetString(PyExc_TypeError, "object does not have array interface");
return NULL;
}
PyArrayInterface *pai = (PyArrayInterface*)PyCObject_AsVoidPtr(ao);
if (pai->two != 2) {
PyErr_SetString(PyExc_TypeError, "object does not have array interface");
Py_DECREF(ao);
return NULL;
}
编译
在setup.py中通过Python版本来区分2和3:
from distutils.core import setup, Extension
import sys
dbr_include_dir = 'e:\\Program Files (x86)\\Dynamsoft\\Barcode Reader 5.2\\Components\\C_C++\\Include'
dbr_lib_dir = 'e:\\Program Files (x86)\\Dynamsoft\Barcode Reader 5.2\\Components\\C_C++\\Lib'
numpy_include_dir = None
if sys.version_info[0] == 2 and sys.version_info[1] == 7:
numpy_include_dir = "F:\\Python27\\Lib\\site-packages\\numpy-1.11.2-py2.7-win32.egg\\numpy\\core\\include\\numpy"
else:
numpy_include_dir = "F:\\Python35\\Lib\\site-packages\\numpy-1.11.2-py3.5-win32.egg\\numpy\\core\\include\\numpy"
module_dbr = Extension('dbr', sources=['dbr.c'], include_dirs=[
numpy_include_dir, dbr_include_dir], library_dirs=[dbr_lib_dir], libraries=['DBRx86'])
setup(name='DynamsoftBarcodeReader',
version='1.0',
description='Python barcode extension',
ext_modules=[module_dbr])
源码