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

Xiao Ling
7 min readMar 17, 2024

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

Step 2: Creating a C Wrapper with CMake

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}")

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

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;

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

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)

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

Originally published at https://www.dynamsoft.com on March 17, 2024.

--

--

Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover