How to Define Python Object Members in C Code

Three years ago, I created a Python extension module for Dynamsoft Barcode Reader C/C++ SDK. The code skeleton has never been changed until recently the SDK updated to v7.0. In the latest barcode SDK, besides the values of barcode symbologies, there are more constant variables needed to be predefined. The original Python barcode extension is initialized only with some methods, and now I have to add some object members. The article shares the code I’ve refactored in order to add Python object members.

PyTypeObject with Python Object Members

To create a Python module in C, we can use Py_InitModule() function which accepts ‘methods’ argument like this:

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},
    {"startVideoMode", startVideoMode, METH_VARARGS, NULL},
    {"stopVideoMode", stopVideoMode, METH_VARARGS, NULL},
    {"appendVideoFrame", appendVideoFrame, METH_VARARGS, NULL},
    {"initLicenseFromLicenseContent", initLicenseFromLicenseContent, METH_VARARGS, NULL},
    {"outputLicenseToString", outputLicenseToString, METH_VARARGS, NULL},
    {"initLicenseFromServer", initLicenseFromServer, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
};

PyObject *module = Py_InitModule("dbr", dbr_methods);

Py_InitModule

PyObject* Py_InitModule(char *name, PyMethodDef *methods)
Return value: Borrowed reference.
Create a new module object based on a name and table of functions, returning the new module object.

Once the module is initialized, we can add objects to the module:

static PyTypeObject DynamsoftBarcodeReaderType = {
    PyVarObject_HEAD_INIT(NULL, 0) "dbr.DynamsoftBarcodeReader", /* tp_name */
    sizeof(DynamsoftBarcodeReader),                              /* tp_basicsize */
    0,                                                           /* tp_itemsize */
    (destructor)DynamsoftBarcodeReader_dealloc,                  /* tp_dealloc */
    0,                                                           /* tp_print */
    0,                                                           /* tp_getattr */
    0,                                                           /* tp_setattr */
    0,                                                           /* tp_reserved */
    0,                                                           /* tp_repr */
    0,                                                           /* tp_as_number */
    0,                                                           /* tp_as_sequence */
    0,                                                           /* tp_as_mapping */
    0,                                                           /* tp_hash  */
    0,                                                           /* tp_call */
    0,                                                           /* tp_str */
    0,                                                           /* tp_getattro */
    0,                                                           /* tp_setattro */
    0,                                                           /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,                    /*tp_flags*/
    "Dynamsoft Barcode Reader objects",                          /* tp_doc */
    0,                                                           /* tp_traverse */
    0,                                                           /* tp_clear */
    0,                                                           /* tp_richcompare */
    0,                                                           /* tp_weaklistoffset */
    0,                                                           /* tp_iter */
    0,                                                           /* tp_iternext */
    dbr_methods,                                                 /* tp_methods */
    dbr_members,                                                 /* tp_members */
    0,                                                           /* tp_getset */
    0,                                                           /* tp_base */
    0,                                                           /* tp_dict */
    0,                                                           /* tp_descr_get */
    0,                                                           /* tp_descr_set */
    0,                                                           /* tp_dictoffset */
    (initproc)DynamsoftBarcodeReader_init,                       /* tp_init */
    0,                                                           /* tp_alloc */
    DynamsoftBarcodeReader_new,                                  /* tp_new */
};

Py_INCREF(&DynamsoftBarcodeReaderType);

PyModule_AddObject(module, "DynamsoftBarcodeReader", (PyObject *)&DynamsoftBarcodeReaderType);

PyModule_AddObject

int PyModule_AddObject(PyObject *module, const char *name, PyObject *value)
Add an object to module as name. This is a convenience function which can be used from the module’s initialization function. This steals a reference to value. Return -1 on error, 0 on success.

The DynamsoftBarcodeReaderType is a PyTypeObject which is also a PyObject:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;
    vectorcallfunc tp_vectorcall;

#ifdef COUNT_ALLOCS
    /* these must be last and never explicitly initialized */
    Py_ssize_t tp_allocs;
    Py_ssize_t tp_frees;
    Py_ssize_t tp_maxalloc;
    struct _typeobject *tp_prev;
    struct _typeobject *tp_next;
#endif
} PyTypeObject;

The PyTypeObject structure contains tp_methods (dbr_methods) and tp_members (dbr_methods). Change the code for module initialization:

static PyMethodDef module_methods[] =
    {
        {NULL}};
PyObject *module = Py_InitModule("dbr", module_methods);

Define a data structure named DynamsoftBarcodeReader:

typedef struct
{
    PyObject_HEAD
    PyObject *COLOR_CLUTERING_MODE;
    PyObject *COLOR_CONVERSION_MODE;
    PyObject *GRAY_SCALE_TRANSFORMATION_MODE;
    PyObject *REGION_PREDETECTION_MODE;
    PyObject *IMAGE_PREPROCESSING_MODE;
    PyObject *TEXTURE_DETECTION_MODE;
    PyObject *TEXTURE_FILTER_MODE;
    PyObject *TEXT_ASSISTED_CORRECTION_MODE;
    PyObject *DPM_CODE_READING_MODE;
    PyObject *DEFORMATION_RESISTING_MODE;
    PyObject *BARCODE_COMPLEMENT_MODE;
    PyObject *BARCODE_COLOR_MODE;
    // GRAY_SCALE_TRANSFORMATION_MODE
    int GTM_INVERTED;
    int GTM_ORIGINAL;
    int GTM_SKIP;
    // Barcode reader handler
    void *hBarcode;
    // Callback function for video mode
    PyObject *py_callback;
} DynamsoftBarcodeReader;

The DynamsoftBarcodeReader structure contains some PyObjects and the reference of Dynamsoft Barcode Reader.

Define the Python object members:

static PyMemberDef dbr_members[] = {
    {"COLOR_CLUTERING_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, COLOR_CLUTERING_MODE), 0,
     NULL},
    {"COLOR_CONVERSION_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, COLOR_CONVERSION_MODE), 0,
     NULL},
    {"GRAY_SCALE_TRANSFORMATION_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, GRAY_SCALE_TRANSFORMATION_MODE), 0,
     NULL},
    {"REGION_PREDETECTION_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, REGION_PREDETECTION_MODE), 0,
     NULL},
    {"IMAGE_PREPROCESSING_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, IMAGE_PREPROCESSING_MODE), 0,
     NULL},
    {"TEXTURE_DETECTION_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, TEXTURE_DETECTION_MODE), 0,
     NULL},
    {"TEXTURE_FILTER_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, TEXTURE_FILTER_MODE), 0,
     NULL},
    {"TEXT_ASSISTED_CORRECTION_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, TEXT_ASSISTED_CORRECTION_MODE), 0,
     NULL},
    {"DPM_CODE_READING_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, DPM_CODE_READING_MODE), 0,
     NULL},
    {"DEFORMATION_RESISTING_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, DEFORMATION_RESISTING_MODE), 0,
     NULL},
    {"BARCODE_COMPLEMENT_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, BARCODE_COMPLEMENT_MODE), 0,
     NULL},
    {"BARCODE_COLOR_MODE", T_OBJECT_EX, offsetof(DynamsoftBarcodeReader, BARCODE_COLOR_MODE), 0,
     NULL},
    {"GTM_INVERTED", T_INT, offsetof(DynamsoftBarcodeReader, GTM_INVERTED), 0,
     NULL},
    {"GTM_ORIGINAL", T_INT, offsetof(DynamsoftBarcodeReader, GTM_ORIGINAL), 0,
     NULL},
    {"GTM_SKIP", T_INT, offsetof(DynamsoftBarcodeReader, GTM_SKIP), 0,
     NULL},
    {NULL} /* Sentinel */
};

Initialize the object members in DynamsoftBarcodeReader_new() function and deallocate the object members in DynamsoftBarcodeReader_dealloc() function.

Build the Python module:

python setup.py build install

Use the Python barcode module in Python project:

from dbr import DynamsoftBarcodeReader
dbr = DynamsoftBarcodeReader()
# dbr.setFurtherModes(dbr.GRAY_SCALE_TRANSFORMATION_MODE, [dbr.GTM_INVERTED, dbr.GTM_ORIGINAL])

python object members

References

Source Code

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