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

Top comments (0)