How to Build Golang Barcode QR Code Reader with Dynamsoft C++ Barcode SDK
Tags: go, programming, docker, cpp
Published: 2022-09-30 00:48:12

In the world of software development, integrating powerful third-party libraries into new or existing projects can significantly enhance functionality and efficiency. For Go developers needing robust barcode scanning capabilities, incorporating the [Dynamsoft C/C++ Barcode SDK](https://www.dynamsoft.com/barcode-reader/sdk-desktop-server/) into a Go module offers a seamless way to leverage advanced barcode reading technology. This article guides you through the process of building a Go module with the Dynamsoft C/C++ Barcode SDK using cgo, enabling you to read barcodes from images effortlessly.

## Prerequisites
- **Go Environment**: Ensure you have Go installed and configured on your system. You can download it from the official [Go website](https://go.dev/dl/).
- **Dynamsoft C/C++ Barcode SDK v9.x**: Download the SDK from the [Dynamsoft website](https://www.dynamsoft.com/barcode-reader/downloads). Make sure to choose the C/C++ version of the SDK. A valid license key is required to use the SDK. You can obtain a free trial license from [here](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr&source=codepool).
- **Development Tools**: For compiling C/C++ code, ensure you have GCC or another compatible C/C++ compiler installed. On Windows, you can use [mingw-w64 GCC](https://www.msys2.org/). On Linux, you can use the default GCC.

## Step 1: Creating a Go Module for Reading Barcodes and QR Codes
As outlined in the [Go documentation](https://go.dev/doc/tutorial/create-module), we create a Go module named `goBarcodeQrSDK` using the terminal.

```bash
mkdir goBarcodeQrSDK
cd goBarcodeQrSDK
go mod init github.com/yushulx/goBarcodeQrSDK
```

Executing this command generates a `go.mod` file, which is essential for tracking your code's dependencies.

```go
module github.com/yushulx/goBarcodeQrSDK

go 1.19
```

## Step 2: Preparing the Dynamsoft SDK for CGo Linking
The Dynamsoft C/C++ Barcode Reader SDK is compatible with multiple platforms, including Windows (x86, x64), Linux (x64, ARM32, ARM64), and macOS (x64, ARM64). For the purposes of this guide, we will focus exclusively on Windows x64 and Linux x64 configurations.

1. Within the `goBarcodeQrSDK` directory, create a `lib` folder. Then, copy the shared libraries and header files from the Dynamsoft C++ Barcode Reader SDK into the lib folder.
    
    ```bash
    |- goBarcodeQrSDK
        |- lib
            |- windows
                |- DynamicPdfx64.dll
                |- DynamsoftBarcodeReaderx64.dll
                |- DynamsoftLicClientx64.dll
                |- DynamsoftLicenseClientx64.dll
                |- vcomp110.dll
            |- linux 
                |- libDynamsoftLicenseClient.so
                |- libDynamsoftBarcodeReader.so
                |- libDynamicPdf.so
                |- libDynamLicenseClient.so
            |- DynamsoftBarcodeReader.h
            |- DynamsoftCommon.h
    ```
2. In the `goBarcodeQrSDK` directory, create two files: `bridge.h` and `bridge.c`. These files are designed to facilitate data conversion between the Dynamsoft Barcode SDK and Go, streamlining the integration process.

    `bridge.h`:

    ```c
    #include <stdio.h>
    #include <stdlib.h>
    #include "DynamsoftBarcodeReader.h"
    
    TextResult *getTextResultPointer(TextResultArray *resultArray, int offset);
    
    LocalizationResult *getLocalizationPointer(TextResult *result);
    
    const char *getText(TextResult *result);
    
    const char *getFormatString(TextResult *result);
    ```

    `bridge.c`:

    ```c
    #include "bridge.h"
    
    TextResult *getTextResultPointer(TextResultArray *resultArray, int offset)
    {
        return resultArray->results[offset];
    }
    
    LocalizationResult *getLocalizationPointer(TextResult *result)
    {
        return result->localizationResult;
    }
    
    const char *getText(TextResult *result)
    {
        return result->barcodeText;
    }
    
    const char *getFormatString(TextResult *result)
    {
        return result->barcodeFormatString;
    }
    ```
      
## Step 3: Writing the CGo Wrapper for the Dynamsoft C++ Barcode Reader SDK
CGo enables Go programs to directly call C code, facilitating the integration of C libraries. To utilize the Dynamsoft SDK from Go, you'll need to create a wrapper.

Within the `goBarcodeQrSDK` directory, create a file named `reader.go`. This file will house the Go functions that invoke the C functions provided by the Dynamsoft Barcode Reader SDK. To accommodate different linking paths for Windows and Linux, you can employ build constraints within your cgo comments. 

```go
    package goBarcodeQrSDK
    
    import (
    	"unsafe"
    
    	/*
    	   #cgo CFLAGS: -I${SRCDIR}/lib
    	   #cgo linux LDFLAGS: -L${SRCDIR}/lib/linux -lDynamsoftBarcodeReader -Wl,-rpath=\$$ORIGIN
    	   #cgo windows LDFLAGS: -L${SRCDIR}/lib/windows -lDynamsoftBarcodeReaderx64
    	   #include <stdlib.h>
    	   #include "DynamsoftBarcodeReader.h"
    	   #include "DynamsoftCommon.h"
    	   #include "bridge.h"
    	*/
    	"C"
    )
```
    
Note that the `bridge.c` file located in your Go package directory will be compiled and linked automatically.

Develop the barcode reading functions that will be invoked from Go. These functions will serve as the interface between your Go application and the Dynamsoft Barcode Reader SDK's capabilities.
- `DBR_InitLicense`: Set a valid license key to activate the SDK.
    
  ```go
        func InitLicense(license string) (int, string) {
          c_license := C.CString(license)
          defer C.free(unsafe.Pointer(c_license))
    
          errorBuffer := make([]byte, 256)
          ret := C.DBR_InitLicense(c_license, (*C.char)(unsafe.Pointer(&errorBuffer[0])), C.int(len(errorBuffer)))
    
          return int(ret), string(errorBuffer)
        }
  ```

- `DBR_CreateInstance`: Create an instance of the barcode reader.
    
  ```go
        type BarcodeReader struct {
          handler unsafe.Pointer
        }
    
        func CreateBarcodeReader() *BarcodeReader {
          handler := C.DBR_CreateInstance()
          if handler == nil {
            return nil
          }
          return &BarcodeReader{handler: handler}
        }
  ```

- `DBR_InitRuntimeSettingsWithFile`: Load a parameter template file for customizing the barcode scanning algorithm.
    
  ```go
        func (reader *BarcodeReader) LoadTemplateFile(params string) (int, string) {
          errorBuffer := make([]byte, 256)
          ret := C.DBR_InitRuntimeSettingsWithFile(reader.handler, C.CString(params), C.CM_OVERWRITE, (*C.char)(unsafe.Pointer(&errorBuffer[0])), C.int(len(errorBuffer)))
          return int(ret), string(errorBuffer)
        }
  ```

- `DBR_DecodeFile`: Read barcodes and QR codes from an image file and return the results.
    
  ```go
        func (reader *BarcodeReader) DecodeFile(filePath string) (int, []Barcode) {
          c_filePath := C.CString(filePath)
          defer C.free(unsafe.Pointer(c_filePath))
          template := C.CString("")
          defer C.free(unsafe.Pointer(template))
    
          var barcodes = []Barcode{}
          ret := C.DBR_DecodeFile(reader.handler, c_filePath, template)
    
          if ret != 0 {
            return int(ret), barcodes
          }
    
          var resultArray *C.TextResultArray
          C.DBR_GetAllTextResults(reader.handler, &resultArray)
    
          if resultArray.resultsCount > 0 {
            for i := 0; i < int(resultArray.resultsCount); i++ {
              barcode := Barcode{}
              result := C.getTextResultPointer(resultArray, C.int(i))
    
              format := C.getFormatString(result)
              barcode.Format = C.GoString(format)
    
              text := C.getText(result)
              barcode.Text = C.GoString(text)
    
              localization := C.getLocalizationPointer(result)
              barcode.X1 = int(localization.x1)
              barcode.Y1 = int(localization.y1)
              barcode.X2 = int(localization.x2)
              barcode.Y2 = int(localization.y2)
              barcode.X3 = int(localization.x3)
              barcode.Y3 = int(localization.y3)
              barcode.X4 = int(localization.x4)
              barcode.Y4 = int(localization.y4)
    
              barcodes = append(barcodes, barcode)
            }
          }
    
          C.DBR_FreeTextResults(&resultArray)
          return int(ret), barcodes
        }
  ```
    
  The `Barcode` struct is defined as follows:
    
  ```go
        type Barcode struct {
          Text   string
          Format string
          X1     int
          Y1     int
          X2     int
          Y2     int
          X3     int
          Y3     int
          X4     int
          Y4     int
        }
  ```
    
    
## Step 4: Building and Testing the Go Module
Now that you have set up your CGo wrapper alongside the barcode reading functions, it's time to compile and test your module using a `_test.go` file.

```go
package goBarcodeQrSDK

import (
	"fmt"
	"testing"
	"time"
)

func TestInitLicense(t *testing.T) {
	ret, _ := InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
	if ret != 0 {
		t.Fatalf(`initLicense("") = %d`, ret)
	}
}

func TestCreateBarcodeReader(t *testing.T) {
	obj := CreateBarcodeReader()
	if obj == nil {
		t.Fatalf(`Failed to create instance`)
	}
}

func TestLoadTemplateFile(t *testing.T) {
	obj := CreateBarcodeReader()
	ret, _ := obj.LoadTemplateFile("template.json")
	if ret != 0 {
		t.Fatalf(`LoadTemplateFile() = %d`, ret)
	}
}

func TestDecodeFile(t *testing.T) {
	obj := CreateBarcodeReader()
	obj.SetParameters("{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_ONED\",\"BF_PDF417\",\"BF_QR_CODE\",\"BF_DATAMATRIX\"],\"BarcodeFormatIds_2\":null,\"Name\":\"sts\",\"RegionDefinitionNameArray\":[\"region0\"]},\"RegionDefinition\":{\"Bottom\":100,\"Left\":0,\"MeasuredByPercentage\":1,\"Name\":\"region0\",\"Right\":100,\"Top\":0}}")
	ret, _ := obj.DecodeFile("test.png")
	if ret != 0 {
		t.Fatalf(`DecodeFile() = %d`, ret)
	}
}
```

Initially, executing `go test` directly may result in an error due to the inability of the system to locate the shared library.

```bash
exit status 0xc0000135
```


To address this issue, prepare and utilize scripts that appropriately configure the library search path for both Windows and Linux systems.

- PowerShell script for Windows:

    ```powershell
    $originalPath = $env:PATH
    
    $dllPath = "lib\windows"
    
    $env:PATH = "$dllPath;$originalPath"
    
    go test
    
    $env:PATH = $originalPath

    ```
- Shell script for Linux:

    ```bash
    #!/bin/bash

    LIB_DIR="lib/linux"
    
    ORIGINAL_LD_LIBRARY_PATH=$LD_LIBRARY_PATH
    
    export LD_LIBRARY_PATH=$LIB_DIR:$LD_LIBRARY_PATH
    
    go test
    
    export LD_LIBRARY_PATH=$ORIGINAL_LD_LIBRARY_PATH

    ```


After running these scripts, you should expect to see the following output:

![Go test](https://www.dynamsoft.com/codepool/img/2022/09/go-test.png)


## Step5: Implementing a Go Barcode and QR Code Reader

Create a `test.go` file and add the following code:

```go
    package main
    
    import (
    	"fmt"
    	"os"
    	"time"
    
    	"github.com/yushulx/goBarcodeQrSDK"
    )
    
    func main() {
    	filename := "test.png"
    	license := "LICENSE-KEY"
    	template := "template.json"
    	
    	ret, errMsg := goBarcodeQrSDK.InitLicense(license)
    	if ret != 0 {
    		fmt.Println(`initLicense(): `, ret)
    		fmt.Println(errMsg)
    		return
    	}
    	obj := goBarcodeQrSDK.CreateBarcodeReader()
    	ret, errMsg = obj.LoadTemplateFile(template)
    	if ret != 0 {
    		fmt.Println(`LoadTemplateFile(): `, ret)
    		fmt.Println(errMsg)
    	}
    	startTime := time.Now()
    	ret, barcodes := obj.DecodeFile(filename)
    	elapsed := time.Since(startTime)
    	fmt.Println("DecodeFile() time cost: ", elapsed)
    
    	if ret != 0 {
    		fmt.Printf(`DecodeFile() = %d`, ret)
    	}
    
    	for i := 0; i < len(barcodes); i++ {
    		barcode := barcodes[i]
    		fmt.Println(barcode.Text)
    		fmt.Println(barcode.Format)
    		fmt.Println(barcode.X1)
    		fmt.Println(barcode.Y1)
    		fmt.Println(barcode.X2)
    		fmt.Println(barcode.Y2)
    		fmt.Println(barcode.X3)
    		fmt.Println(barcode.Y3)
    		fmt.Println(barcode.X4)
    		fmt.Println(barcode.Y4)
    		fmt.Println("--------------")
    	}
    }
    
```
    
Remember to substitute `LICENSE-KEY` with your own license key.

Utilize the provided scripts to load the necessary libraries and execute your program on Windows and Linux.

- PowerShell script for Windows:

  ```powershell
        $originalPath = $env:PATH

        $GOPATH = $(go env GOPATH)
        
        $PACKAGE_PATH = Get-ChildItem -Path "$GOPATH\pkg\mod\github.com\yushulx" -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
        
        $ASSEMBLY_PATH = "$PACKAGE_PATH\lib\windows"
        Write-Host "ASSEMBLY_PATH set to $ASSEMBLY_PATH"
        # Update PATH to include the assembly path
        $env:PATH = "$ASSEMBLY_PATH;" + $env:PATH
        
        # Run your Go application
        go run test.go test.png
        
        $env:PATH = $originalPath

  ```
        
  ![cgo barcpde reader on Windows](https://www.dynamsoft.com/codepool/img/2024/03/go-c-barcode-windows.png)

- Shell script for Linux:

  ```bash
        #!/bin/bash

        # Save the original PATH
        originalPath=$LD_LIBRARY_PATH
        
        # Get the GOPATH
        GOPATH=$(go env GOPATH)
        
        # Find the path to the shared libraries
        PACKAGE_PATH=$(find "$GOPATH/pkg/mod/github.com/yushulx" -mindepth 1 -maxdepth 1 -type d | sort -r | head -n 1)
        echo "PACKAGE_PATH set to $PACKAGE_PATH"
        
        ASSEMBLY_PATH="$PACKAGE_PATH/lib/linux"
        echo "ASSEMBLY_PATH set to $ASSEMBLY_PATH"
        
        export LD_LIBRARY_PATH="$ASSEMBLY_PATH:$originalPath"
        
        # Run your Go application
        go run test.go test.png
        
        # Restore the original PATH
        export LD_LIBRARY_PATH=$originalPath
  ```
        
  ![cgo barcode reader on Linux](https://www.dynamsoft.com/codepool/img/2024/03/go-c-barcode-linux.png)
    

## Step 6: Deploying the Go Barcode and QR Code Reader to Docker
1. Create a `Dockerfile` file in the root directory of your project. This file will define the environment for running your Go application within a Docker container.

    ```dockerfile
    FROM golang:1.19
    COPY . /usr/src/myapp
    WORKDIR /usr/src/myapp/example/command-line
    RUN cp -r ../../lib/linux/* /usr/lib/x86_64-linux-gnu/
    RUN cp template.json /usr/local/bin/
    RUN go mod download
    RUN go build -v -o /usr/local/bin/reader
    CMD [ "reader"]
    ```

2. With the `Dockerfile` in place, build your Docker image using the `docker build` command. This process packages your application and its dependencies into a Docker image.

    ```bash
    docker build -t golang-barcode-qr-reader .
    ```

3. Once your Docker container is running, you can use your Go application to read barcodes and QR codes from local image files that are mounted to the container.

    ```bash
    docker run -it --rm -v <image-folder>:/app golang-barcode-qr-reader reader /app/<image-file> <license-key> <template-file>
    ```

    ![Golang barcode QR code reader](https://www.dynamsoft.com/codepool/img/2024/03/golang-barcode-qr-code-reader.png)

### Docker Image with Golang Barcode QR Reader
[https://hub.docker.com/r/yushulx/golang-barcode-qr-reader](https://hub.docker.com/r/yushulx/golang-barcode-qr-reader)

```bash
docker run -it --rm -v <image-folder>:/app yushulx/golang-barcode-qr-reader:latest reader /app/<image-file> <license-key> <template-file>
```

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