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