Building a Go Module with C API of Dynamsoft Barcode SDK v9.x

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 Barcode SDK 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 C API of Dynamsoft Barcode SDK v9.x 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.
  • Dynamsoft C/C++ Barcode SDK v9.x: Download the SDK from the Dynamsoft website. 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.
  • 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. 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, we create a Go module named goBarcodeQrSDK using the terminal.

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.

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 Barcode Reader SDK into the lib folder.

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

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

     #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/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.

  1. 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.

     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.

  2. 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.

        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 barcodes and QR codes 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
        }
      

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.

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.

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:

      $originalPath = $env:PATH
        
      $dllPath = "lib\windows"
        
      $env:PATH = "$dllPath;$originalPath"
        
      go test
        
      $env:PATH = $originalPath
    
    
  • Shell script for Linux:

      #!/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

Step5: Implementing a Go Barcode and QR Code Reader

  1. 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 := "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.

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

    • PowerShell script for Windows:

        $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

    • Shell script for Linux:

        #!/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

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.

     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.

     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.

     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

Docker Image with Golang Barcode QR Reader

https://hub.docker.com/r/yushulx/golang-barcode-qr-reader

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