如何讓C/C++代碼同時兼容Python2和Python3

Python2Python3的差別很大,這是爲何不少人都不肯意升級的緣由。若是你用C/C++爲Python2寫過擴展模塊,那麼直接用Python3來編譯是通不過的。這篇文章分享下如何編寫兼容的C/C++代碼。html

環境搭建

  • 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++兼容代碼。python

#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接口的使用

這裏分享下我碰到的接口變化問題。git

String

Python2用的接口是PyString_FromString,而Python3用的是PyUnicode_FromFormatgithub

#if defined(IS_PY3K)
result = PyUnicode_FromFormat("%s", tmp->pBarcodeData);
#else
result = PyString_FromString(tmp->pBarcodeData);
#endif

NumPy相關數據獲取:buffer, width, height, stride

這塊部分網上的信息比較少,我經過源碼找到了方法。windows

#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:app

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-windowside

相關文章
相關標籤/搜索