How to Port C/C++ Barcode Extension to Python 3

When porting code from Python 2 to Python 3, it is not as easy as you imagined no matter whether the primary programming language is Python or C/C++. Re-compiling the Dynamsoft Barcode extension for Python 3 is a challenge because I only found a few of documentation and resources online. Luckily, I succeeded in using Python 3 compatible APIs after reading the source code of Numpy.

Barcode Reader SDK for Python 3

The following content is to illustrate how to make C code compatible with both Python 2 and Python 3.

Prerequisites

  • Dynamsoft Barcode Reader 5.2 for Windows.

    Copy Dynamsoft\Barcode Reader 5.2\Components\C_C++\Redist\DynamsoftBarcodeReaderx86.dll to Python35\Lib\site-packages.

  • 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%
    

Initialize the extension for Python 2 and Python 3

Create dbr.c. According to the tutorial - Porting Extension Modules to Python 3, we can initialize the module as follows:

#if PY_MAJOR_VERSION >= 3
#ifndef IS_PY3K
#define IS_PY3K 1
#endif
#endif

struct module_state {
    PyObject *error;
};

#if defined(IS_PY3K)
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
static struct module_state _state;
#endif

static PyMethodDef dbr_methods[] =
{
    {"create", create, METH_VARARGS, NULL},
    {"destroy", destroy, METH_VARARGS, NULL},
    {"initLicense", initLicense, METH_VARARGS, NULL},
    {"decodeFile", decodeFile, METH_VARARGS, NULL},
    {"decodeBuffer", decodeBuffer, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
};

#if defined(IS_PY3K)

static int dbr_traverse(PyObject *m, visitproc visit, void *arg) {
    Py_VISIT(GETSTATE(m)->error);
    return 0;
}

static int dbr_clear(PyObject *m) {
    Py_CLEAR(GETSTATE(m)->error);
    return 0;
}

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "dbr",
    NULL,
    sizeof(struct module_state),
    dbr_methods,
    NULL,
    dbr_traverse,
    dbr_clear,
    NULL
};

#define INITERROR return NULL

PyMODINIT_FUNC
PyInit_dbr(void)

#else
#define INITERROR return
void
initdbr(void)
#endif
{
#if defined(IS_PY3K)
    PyObject *module = PyModule_Create(&moduledef);
#else
    PyObject *module = Py_InitModule("dbr", dbr_methods);
#endif

    if (module == NULL)
        INITERROR;
    struct module_state *st = GETSTATE(module);

    st->error = PyErr_NewException("dbr.Error", NULL, NULL);
    if (st->error == NULL) {
        Py_DECREF(module);
        INITERROR;
    }

#if defined(IS_PY3K)
    return module;
#endif
}

Replace PyString_FromString with PyUnicode_FromFormat

The barcode results have to be converted from C string to PyObject:

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

Get image width, height, and buffer in C/C++

This part is totally different. You can download the source code of NumPy and read numpy/core/src/multiarray/ctors.c.

Here is the C/C++ code:

#if defined(IS_PY3K)
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

Add include and lib paths to setup.py

You have to replace the paths with yours before building the extension:

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

Build the barcode extension module for Python 3

python3 setup.py build install

Command line app

Create test.py:

import os.path
import dbr
import cv2

def initLicense(license):
    dbr.initLicense(license)

def decodeFile(fileName):
    formats = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000 # 1D, QRCODE, PDF417, DataMatrix
    results = dbr.decodeFile(fileName, formats)
    
    for result in results:
        print("barcode format: " + result[0])
        print("barcode value: " + result[1])

def decodeBuffer(image):
    formats = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000 # 1D, QRCODE, PDF417, DataMatrix
    results = dbr.decodeBuffer(image, formats)
    
    for result in results:
        print("barcode format: " + result[0])
        print("barcode value: " + result[1])

if __name__ == "__main__":
    barcode_image = input("Enter the barcode file: ")
    if not os.path.isfile(barcode_image):
        print("It is not a valid file.")
    else:
        initLicense("Contact support@dynamsoft.com to get a valid license.")
        decodeFile(barcode_image)

Run the app:

python3 test.py

python3 barcode

Source code

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