DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

Integrating Dynamsoft's C++ Barcode SDK v10 into Go Module with a C Wrapper

Starting with version 10, the Dynamsoft Barcode Reader SDK has been entirely rewritten in C++, providing exclusively C++ APIs. This marks a significant departure from version 9.x, which offered both C and C++ APIs, rendering our Go module designed for version 9.x incompatible with version 10. The incompatibility arises because cgo does not directly support calling C++ APIs. In this article, we will demonstrate how to create a C wrapper to call the C++ API of Dynamsoft Barcode Reader SDK v10. Subsequently, we'll only need to update the import "C" statement to link the C wrapper library, avoiding any modifications to the existing Go wrapper code.

Prerequisites

Step 1: Getting Started with the C++ Barcode SDK

The Dynamsoft Barcode Reader v10.x includes a set of C++ libraries (for Windows x86/x64 and Linux x64), header files, and templates. After coping these components into the lib folder, the directory structure should look like this:

|- goBarcodeQrSDK
    |- lib
        |- windows
            |- DBR-PresetTemplates.json
            |- DynamicImagex64.dll
            |- DynamicPdfCorex64.dll
            |- DynamicPdfx64.dll
            |- DynamsoftBarcodeReaderx64.dll
            |- DynamsoftBarcodeReaderx64.lib
            |- DynamsoftCaptureVisionRouterx64.dll
            |- DynamsoftCaptureVisionRouterx64.lib
            |- DynamsoftCorex64.dll
            |- DynamsoftCorex64.lib
            |- DynamsoftImageProcessingx64.dll
            |- DynamsoftImageProcessingx64.lib
            |- DynamsoftLicensex64.dll
            |- DynamsoftLicensex64.lib
            |- DynamsoftUtilityx64.dll
            |- DynamsoftUtilityx64.lib
            |- vcomp140.dll
        |- linux 
            |- DBR-PresetTemplates.json
            |- libbridge.so
            |- libDynamicImage.so
            |- libDynamicPdf.so
            |- libDynamicPdfCore.so
            |- libDynamsoftBarcodeReader.so
            |- libDynamsoftCaptureVisionRouter.so
            |- libDynamsoftCore.so
            |- libDynamsoftImageProcessing.so
            |- libDynamsoftLicense.so
            |- libDynamsoftUtility.so
        |- DynamsoftBarcodeReader.h
        |- DynamsoftCaptureVisionRouter.h
        |- DynamsoftCodeParser.h
        |- DynamsoftCore.h
        |- DynamsoftDocumentNormalizer.h
        |- DynamsoftImageProcessing.h
        |- DynamsoftLabelRecognizer.h
        |- DynamsoftLicense.h
        |- DynamsoftUtility.h
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating a C Wrapper with CMake

CMake C wrapper for C++

We will develop a C wrapper to bridge the C++ API of the Dynamsoft Barcode Reader SDK v10. This wrapper will be constructed using CMake. The structure of the CMakeLists.txt file is as follows:

cmake_minimum_required(VERSION 3.0.0)

project(bridge VERSION 0.1.0)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(TARGET_LIB_DIR "${PROJECT_SOURCE_DIR}/../linux/")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(TARGET_LIB_DIR "${PROJECT_SOURCE_DIR}/../windows/")
endif()

link_directories(${TARGET_LIB_DIR})
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/../")
add_library(bridge SHARED bridge.cpp)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    target_link_libraries (${PROJECT_NAME} "DynamsoftLicense" "DynamsoftBarcodeReader" "DynamsoftCaptureVisionRouter" "DynamsoftCore")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    if(CMAKE_CL_64)
        target_link_libraries (${PROJECT_NAME} "DynamsoftLicensex64" "DynamsoftBarcodeReaderx64" "DynamsoftCaptureVisionRouterx64" "DynamsoftCorex64")
    endif()
endif()

# Copy the built library to the target directory
add_custom_command(TARGET bridge POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy
    "$<TARGET_FILE:bridge>"
    "${TARGET_LIB_DIR}")
Enter fullscreen mode Exit fullscreen mode

In bridge.h, we declare C functions for cgo to invoke, and these are implemented in bridge.cpp. As we compile the C wrapper into a shared library, we utilize the add_custom_command in CMake to automate copying the compiled library into the desired directory.

Reflecting on the C API provided by Dynamsoft Barcode Reader v9.x, we will declare similar C functions in bridge.h:

#ifndef C_BRIDGING_H
#define C_BRIDGING_H

#if defined(_WIN32) || defined(_WIN64)
#define C_API __declspec(dllexport)
#else
#define C_API __attribute__((visibility("default")))
#endif

typedef struct
{
    int x1;
    int y1;
    int x2;
    int y2;
    int x3;
    int y3;
    int x4;
    int y4;

} LocalizationResult;

typedef struct
{
    char *barcodeFormatString;
    char *barcodeText;
    LocalizationResult *localizationResult;
    char reserved[44];
} TextResult;

typedef struct
{
    int resultsCount;
    TextResult *results;
} TextResultArray;

typedef enum ConflictMode
{
    CM_IGNORE = 1,
    CM_OVERWRITE = 2

} ConflictMode;

typedef struct
{
    void *cvr;
    void *result;
} BarcodeReader;

#ifdef __cplusplus
extern "C"
{
#endif
    // Create dbr9.x-like API for dbr10.x
    C_API int DBR_InitLicense(const char *pLicense, char errorMsgBuffer[], const int errorMsgBufferLen);
    C_API int DBR_DecodeFile(void *barcodeReader, const char *pFileName, const char *pTemplateName);
    C_API void *DBR_CreateInstance();
    C_API void DBR_DestroyInstance(void *barcodeReader);
    C_API const char *DBR_GetVersion();
    C_API int DBR_GetAllTextResults(void *barcodeReader, TextResultArray **pResults);
    C_API void DBR_FreeTextResults(TextResultArray **pResults);
    C_API int DBR_InitRuntimeSettingsWithString(void *barcodeReader, const char *content, const ConflictMode conflictMode, char errorMsgBuffer[], const int errorMsgBufferLen);
    C_API int DBR_InitRuntimeSettingsWithFile(void *barcodeReader, const char *pFilePath, const ConflictMode conflictMode, char errorMsgBuffer[], const int errorMsgBufferLen);

    // The interop functions for Go
    C_API TextResult *getTextResultPointer(TextResultArray *resultArray, int offset);
    C_API LocalizationResult *getLocalizationPointer(TextResult *result);
    C_API const char *getText(TextResult *result);
    C_API const char *getFormatString(TextResult *result);
#ifdef __cplusplus
}
#endif

#endif
Enter fullscreen mode Exit fullscreen mode

To ensure these C functions are properly exported on both Windows and Linux, we employ __declspec(dllexport) and __attribute__((visibility("default"))), respectively for Windows and Linux.

At the start of bridge.cpp, we include necessary C++ headers and declare the use of relevant namespaces:

#include <iostream>
#include <string>
#include <cstring>
#include "bridge.h"
#include "DynamsoftCaptureVisionRouter.h"

using namespace std;
using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
Enter fullscreen mode Exit fullscreen mode

Following this setup, we proceed to implement the declared C functions.

  • DBR_InitLicense: In v10.x, the license is initialized by calling CLicenseManager::InitLicense. This initialization is not only used for the barcode reader but also for other products like the Dynamsoft Label Recognizer and Dynamsoft Document Normalizer.

    C_API int DBR_InitLicense(const char *pLicense, char errorMsgBuffer[], const int errorMsgBufferLen)
    {
        int ret = CLicenseManager::InitLicense(pLicense, errorMsgBuffer, 512);
        if (ret != 0)
        {
            cout << "Error: " << errorMsgBuffer << endl;
        }
        return ret;
    }
    
  • DBR_CreateInstance: This function creates an instance of BarcodeReader, which includes pointers to the CCaptureVisionRouter class and CCapturedResult class. Since classes are not supported in C, we use void * to represent these pointers and cast them to the actual class pointers when necessary.

    C_API void *DBR_CreateInstance()
    {
        BarcodeReader *barcodeReader = new BarcodeReader;
        CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
        barcodeReader->cvr = cvr;
        barcodeReader->result = NULL;
        return (void *)barcodeReader;
    }
    
  • DBR_DestroyInstance: Releases the memory allocated for the BarcodeReader instance.

    C_API void DBR_DestroyInstance(void *barcodeReader)
    {
        if (barcodeReader != NULL)
        {
            BarcodeReader *reader = (BarcodeReader *)barcodeReader;
            if (reader->cvr != NULL)
            {
                delete (CCaptureVisionRouter *)reader->cvr;
                reader->cvr = NULL;
            }
    
            if (reader->result != NULL)
            {
                ((CCapturedResult *)reader->result)->Release();
                reader->result = NULL;
            }
    
            delete reader;
            barcodeReader = NULL;
        }
    }
    
  • DBR_DecodeFile: Decode barcodes from an image file. The results are stored in BarcodeReader->result. In v10.x, the barcode decoding function has been replaced by CCaptureVisionRouter::Capture. Depending on your template configuration, the results could vary, including data types such as text, barcode, or quadrilateral.

    C_API int DBR_DecodeFile(void *barcodeReader, const char *pFileName, const char *pTemplateName)
    {
        BarcodeReader *reader = (BarcodeReader *)barcodeReader;
        if (!reader || !reader->cvr)
            return -1;
    
        CCapturedResult *result = ((CCaptureVisionRouter *)reader->cvr)->Capture(pFileName);
        int errorCode = result->GetErrorCode();
        if (result->GetErrorCode() != 0)
        {
            cout << "Error: " << result->GetErrorCode() << "," << result->GetErrorString() << endl;
        }
    
        reader->result = result;
    
        return errorCode;
    }
    
  • DBR_GetVersion: Retrieves the version number of the Dynamsoft Barcode Reader SDK.

    C_API const char *DBR_GetVersion()
    {
        return CBarcodeReaderModule::GetVersion();
    }
    
  • DBR_GetAllTextResults: Extracts barcode text, format, and coordinates from the results. The CapturedResultItemType::CRIT_BARCODE enum is used to filter for the barcode data type.

    C_API int DBR_GetAllTextResults(void *barcodeReader, TextResultArray **pResults)
    {
        BarcodeReader *reader = (BarcodeReader *)barcodeReader;
        if (!reader || !reader->cvr || !reader->result)
            return -1;
    
        CCapturedResult *result = (CCapturedResult *)reader->result;
    
        int capturedResultItemCount = result->GetItemsCount();
        if (capturedResultItemCount == 0)
            return -1;
    
        TextResultArray *textResults = (TextResultArray *)calloc(1, sizeof(TextResultArray));
        textResults->resultsCount = capturedResultItemCount;
        textResults->results = (TextResult *)calloc(capturedResultItemCount, sizeof(TextResult));
        *pResults = textResults;
    
        for (int j = 0; j < capturedResultItemCount; j++)
        {
            const CCapturedResultItem *capturedResultItem = result->GetItem(j);
            CapturedResultItemType type = capturedResultItem->GetType();
            if (type == CapturedResultItemType::CRIT_BARCODE)
            {
                const CBarcodeResultItem *barcodeResultItem = dynamic_cast<const CBarcodeResultItem *>(capturedResultItem);
                char *barcodeFormatString = (char *)barcodeResultItem->GetFormatString();
                char *barcodeText = (char *)barcodeResultItem->GetText();
                textResults->results[j].barcodeFormatString = (char *)malloc(strlen(barcodeFormatString) + 1);
                strcpy(textResults->results[j].barcodeFormatString, barcodeFormatString);
                textResults->results[j].barcodeText = (char *)malloc(strlen(barcodeText) + 1);
                strcpy(textResults->results[j].barcodeText, barcodeText);
    
                CPoint *points = barcodeResultItem->GetLocation().points;
                textResults->results[j].localizationResult = (LocalizationResult *)malloc(sizeof(LocalizationResult));
                textResults->results[j].localizationResult->x1 = points[0][0];
                textResults->results[j].localizationResult->y1 = points[0][1];
                textResults->results[j].localizationResult->x2 = points[1][0];
                textResults->results[j].localizationResult->y2 = points[1][1];
                textResults->results[j].localizationResult->x3 = points[2][0];
                textResults->results[j].localizationResult->y3 = points[2][1];
                textResults->results[j].localizationResult->x4 = points[3][0];
                textResults->results[j].localizationResult->y4 = points[3][1];
            }
        }
    
        result->Release();
        reader->result = NULL;
    
        return 0;
    }
    
  • DBR_FreeTextResults: Releases the memory allocated for the TextResultArray.

    C_API void DBR_FreeTextResults(TextResultArray **pResults)
    {
        if (pResults)
        {
            if (*pResults)
            {
                if ((*pResults)->results)
                {
                    for (int i = 0; i < (*pResults)->resultsCount; i++)
                    {
                        if ((*pResults)->results[i].barcodeFormatString)
                        {
                            free((*pResults)->results[i].barcodeFormatString);
                        }
                        if ((*pResults)->results[i].barcodeText)
                        {
                            free((*pResults)->results[i].barcodeText);
                        }
    
                        if ((*pResults)->results[i].localizationResult)
                        {
                            free((*pResults)->results[i].localizationResult);
                        }
                    }
                    free((*pResults)->results);
                }
            }
        }
    }
    
  • DBR_InitRuntimeSettingsWithString: Initializes runtime settings with a JSON string.

    C_API int DBR_InitRuntimeSettingsWithString(void *barcodeReader, const char *content, const ConflictMode conflictMode, char errorMsgBuffer[], const int errorMsgBufferLen)
    {
        BarcodeReader *reader = (BarcodeReader *)barcodeReader;
        if (!reader || !reader->cvr)
            return -1;
    
        int ret = ((CCaptureVisionRouter *)reader->cvr)->InitSettings(content, errorMsgBuffer, errorMsgBufferLen);
    
        if (ret != 0)
        {
            cout << "Error: " << errorMsgBuffer << endl;
        }
    
        return ret;
    }
    

    The template parameters and structure have been changed in v10.x. Below is a simple example:

    {
      "CaptureVisionTemplates": [
        {
          "Name": "cv0",
          "ImageROIProcessingNameArray": [
            "roi-read-barcodes"
          ],
          "Timeout": 10000
        }
      ],
      "TargetROIDefOptions": [
        {
          "Name": "roi-read-barcodes",
          "TaskSettingNameArray": [
            "task-read-barcodes"
          ]
        }
      ],
      "BarcodeReaderTaskSettingOptions": [
        {
          "Name": "task-read-barcodes",
          "ExpectedBarcodesCount": 0,
          "BarcodeFormatIds": [ "BF_DATAMATRIX" ]
        }
      ]
    }
    

    Note: The DBR-PresetTemplates.json file, along with the shared libraries, will be loaded by default. If you invoke the DBR_InitRuntimeSettingsWithString function, the settings specified in the JSON string will overwrite the default settings.

After completing the bridge.cpp implementation, we can compile the C wrapper into shared libraries for Windows and Linux using the commands provided below:

  • Windows:

    mkdir build
    cd build
    cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
    cmake --build . --config Release
    
  • Linux:

    mkdir build
    cd build
    cmake ..
    cmake --build . --config Release
    

Step 3: Integrating the C Wrapper into the Existing Go Module

To incorporate the C wrapper into our Go module, we'll need to adjust the reader.go file to link against the C wrapper's shared library. This involves the following modifications:

import (
    /*
       #cgo CXXFLAGS: -std=c++11
       #cgo CFLAGS: -I${SRCDIR}/lib -I${SRCDIR}/lib/bridge
       #cgo linux LDFLAGS: -L${SRCDIR}/lib/linux -lbridge  -Wl,-rpath=\$$ORIGIN
       #cgo windows LDFLAGS: -L${SRCDIR}/lib/windows -lbridge
       #include <stdlib.h>
       #include "bridge.h"
    */
    "C"
)
Enter fullscreen mode Exit fullscreen mode

Additionally, it's necessary to update the test code with a new template string and your personal license key within the goBarcodeQrSDK_test.go file:

var jsonString = `{
    "CaptureVisionTemplates": [
      {
        "Name": "cv0",
        "ImageROIProcessingNameArray": [
          "roi-read-barcodes"
        ],
        "Timeout": 10000
      }
    ],
    "TargetROIDefOptions": [
      {
        "Name": "roi-read-barcodes",
        "TaskSettingNameArray": [
          "task-read-barcodes"
        ]
      }
    ],
    "BarcodeReaderTaskSettingOptions": [
      {
        "Name": "task-read-barcodes",
        "ExpectedBarcodesCount": 0,
        "BarcodeFormatIds": [ "BF_DEFAULT" ]
      }
    ]
  }`

ret, _ := InitLicense("LICENSE-KEY")
ret, _ := obj.SetParameters(jsonString)
Enter fullscreen mode Exit fullscreen mode

Finally, we can compile and execute the Go module as follows:

  • Windows:

    run_windows_test.ps1
    
  • Linux:

    ./run_linux_test.sh
    

Source Code

https://github.com/yushulx/goBarcodeQrSDK/tree/dbr10

Please leave your appreciation by commenting on this post!

Sure thing

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay