Integrating Dynamsoft's C++ Barcode SDK v10 into Go Module with a C Wrapper
Tags: go, cpp, cgo
Published: 2024-03-18 01:57:42

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
- [Go Environment](https://go.dev/dl/)
- [Dynamsoft C/C++ Barcode SDK v10.x](https://www.dynamsoft.com/barcode-reader/downloads) and [license key](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr&source=codepool)
- **GCC**

## 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:

```bash
|- 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

![CMake C wrapper for C++](https://www.dynamsoft.com/codepool/img/2024/03/go-c-wrapper-cpp-barcode.png)


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
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`:

```c
#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:

```cpp
#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
    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
    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
    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
    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
    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
    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
    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
    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:

    ```json
    {
      "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:

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

    ```bash
    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:

```go
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:

```go
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:

    ```bash
    run_windows_test.ps1
    ```
- Linux:

    ```bash
    ./run_linux_test.sh
    ```

## Source Code
[https://github.com/yushulx/goBarcodeQrSDK/tree/dbr10](https://github.com/yushulx/goBarcodeQrSDK/tree/dbr10)