How to Build Golang Barcode QR Code Reader with Dynamsoft C++ Barcode SDK

Xiao Ling
5 min readSep 28, 2022

--

This article aims to help Go developers to build barcode QR code reader applications with Dynamsoft C++ Barcode SDK on Windows and Linux. You will see how to interoperate Golang with C++ code using cgo, as well as how to deploy the application to Docker.

Prerequisites

To build cgo on Windows, you need to install mingw-w64 GCC.

Get a free trial license from here.

Creating a Go Module for Reading Barcode and QR Code

Dynamsoft C++ Barcode Reader SDK supports Windows(x86, x64), Linux(x64, ARM32, ARM64), and macOS(x64, ARM64). Here we only focus on Windows and Linux.

Initialize a Go Module Project

According to the Go documentation, we create a Go module named goBarcodeQrSDK in the terminal:

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

The command creates a go.mod file which tracks your code's dependencies.

module github.com/yushulx/goBarcodeQrSDK

go 1.19

Link Dynamsoft C++ Barcode Reader Libraries to Go

  1. Create a lib folder.
  2. Copy shared libraries and header files of Dynamsoft C++ Barcode Reader SDK to the lib folder. Change DynamsoftBarcodeReaderx64.dll to DynamsoftBarcodeReader.dll to make the linking work on both Windows and Linux.
  3. In your text editor, create a reader.go file.
  4. Add cgo LDFLAGS and include C++ header files in the reader.go file.
package goBarcodeQrSDK

import (
"unsafe"

// #include <stdlib.h>
// #include <lib/DynamsoftBarcodeReader.h>
// #include <lib/DynamsoftCommon.h>
// #include <lib/bridge.c>
// #cgo LDFLAGS: -L ./lib -lDynamsoftBarcodeReader -Wl,-rpath=./lib
"C"
)

Calling Dynamsoft C++ Barcode Reader Functions in Go

To implement a barcode QR code reader with Dynamsoft Barcode SDK, we need to call the following functions:

  • DBR_InitLicense: Set a valid license key to activate the SDK.
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.
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.
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 barcode QR code from an image file and return the results.
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:

type Barcode struct {
Text string
Format string
X1 int
Y1 int
X2 int
Y2 int
X3 int
Y3 int
X4 int
Y4 int
}

To simplify data conversion between C++ and Go, we implement some C++ pointer operations in the bridge.c file.

#include <stdio.h>
#include <stdlib.h>
#include "DynamsoftBarcodeReader.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;
}

Before using these Go functions, you need to test them in a _test.go file.

Test the Go Module

We create a goBarcodeQrSDK_test.go file to test the Go module.

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

Run go test.

If the test is successful, you can use the Go module in your project.

Implementing Golang Barcode QR Code Reader

Create a new module in another directory and add the goBarcodeQrSDK module as a dependency locally.

mkdir example
go mod init example.com/test
go mod edit -replace github.com/yushulx/goBarcodeQrSDK=../
go mod tidy

The go.mod file should look like this:

module example.com/test

go 1.19

replace github.com/yushulx/goBarcodeQrSDK => ../

require github.com/yushulx/goBarcodeQrSDK v0.0.0-00010101000000-000000000000

To use a published module, omit the replace directive and use a require directive with a tagged version number at the end.

- replace github.com/yushulx/goBarcodeQrSDK => ../
+ require github.com/yushulx/goBarcodeQrSDK v1.0.3

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

package main

import (
"fmt"
"os"
"time"

"github.com/yushulx/goBarcodeQrSDK"
)

func main() {
filename := "test.png"
license := "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="
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("--------------")
}
}

We use Windows and WSL to test the barcode QR code reader for Windows and Linux respectively.

go run .

As you can see, there is no error when running on Windows. However, when running on Linux, the dependent shared library cannot be found. Because the rpath is set to the lib directory, to find the shared library, we can build the executable file to the directory where the lib folder is located.

go build -o ../reader
cd ..
./reader test.png

Deploying Golang Barcode QR Reader to Docker

  1. Create a Dockerfile file in the root directory of the project:
FROM golang:1.19
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp/example
COPY ../lib/ /usr/lib/x86_64-linux-gnu/
RUN cp test.png /usr/local/bin/
RUN cp template.json /usr/local/bin/
RUN go mod download
RUN go build -v -o /usr/local/bin/reader
CMD [ "reader"]

2. Build the Docker image:

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

3. Read barcode and QR code from a local image file:

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

Published Docker Image

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

Originally published at https://www.dynamsoft.com on September 28, 2022.

--

--

Xiao Ling
Xiao Ling

Written by Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover

No responses yet