如何让C/C++代码同时兼容Python2和Python3

Python2Python3的差异很大,这是为什么很多人都不愿意升级的原因。如果你用C/C++为Python2写过扩展模块,那么直接用Python3来编译是通不过的。这篇文章分享下如何编写兼容的C/C++代码。

环境搭建

  • Python 3.5.0
  • 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])

源码

https://github.com/dynamsoft-dbr/python-barcode-windows

转载于:https://my.oschina.net/yushulx/blog/1558151

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值