How to Build a Go Barcode and QR Code Reader with Dynamsoft Capture Vision SDK

The goBarcodeQrSDK module reads barcodes and QR codes from images, PDFs, and TIFFs in Go by wrapping the Dynamsoft Capture Vision SDK (Barcode Reader 11.6.10) C++ API behind a pure-C bridge compiled with CMake and called through cgo. Because cgo cannot call C++ directly, the project compiles src/bridge.cpp into bridge.dll / libbridge.so / libbridge.dylib and exposes DBR_* functions that Go calls. The whole pipeline builds and runs on Windows x64, Linux x64, and macOS (Apple Silicon + Intel), and is verified by a cross-platform GitHub Actions workflow.

What you’ll build: a Go module (github.com/yushulx/goBarcodeQrSDK/v2) that wraps CCaptureVisionRouter, plus a command-line reader and web example, with DecodeFile, DecodeStream, and multi-page PDF support — and a Docker image that runs the reader in a container.

Key Takeaways

  • The DCV SDK is C++-only from version 10 onwards; cgo needs a C bridge (bridge.h + src/bridge.cpp) compiled as a shared library with CMake.
  • Go calls DBR_DecodeFile/DBR_DecodeFileInMemory; both delegate to CCaptureVisionRouter::CaptureMultiPages, which reads single images and multi-page PDF/TIFF files in one call.
  • The Barcode result struct carries Text, Format, four corner coordinates (X1Y4), and PageId for multi-page documents.
  • Shared libraries are resolved at runtime via PATH (Windows), LD_LIBRARY_PATH (Linux), or @rpath (macOS); the repo ships run_windows_test.ps1, run_linux_test.sh, and run_mac_test.sh.
  • A three-OS GitHub Actions workflow rebuilds the bridge and runs go test plus both examples to catch platform regressions automatically.

Common Developer Questions

Can cgo call the Dynamsoft C++ SDK directly?

No. cgo can only call C (and using C++ struct layout across the boundary is unsafe for this API), so the module adds a C bridge: bridge.h declares plain DBR_* functions and src/bridge.cpp implements them in extern "C", forwarding to CCaptureVisionRouter. Go then links only the bridge library.

How do you read barcodes from images and PDFs in Go?

Call obj.DecodeFile(path) (this detects .pdf and reroutes to the PDFium renderer) or obj.DecodeStream(bytes). Both return ([]Barcode, error); each barcode includes PageId so you know which page of a multi-page PDF or TIFF it came from.

Which barcode formats does the module support?

The default template enables 1D and 2D barcodes including QR Code, Micro QR, Data Matrix, PDF417 (and Micro PDF417), Aztec, DotCode, and all standard linear formats. You control the set via template.json (BarcodeFormatIds such as BF_QR_CODE, BF_DATAMATRIX, BF_ALL).

Why do I need CMake and a C++ compiler?

You must build the bridge shared library (bridge.dll, libbridge.so, libbridge.dylib) before using the module. Windows needs Visual Studio or MinGW, Linux needs g++, and macOS needs Xcode Command Line Tools — CMake generates the build for all three.

Prerequisites

  • Go Environment: Ensure you have Go installed and configured on your system. You can download it from the official Go website.
  • Dynamsoft Capture Vision SDK: Download the SDK from the Dynamsoft website. A valid license key is required to use the SDK. You can obtain a free trial license from Dynamsoft.
  • CMake: Required to build the C++ bridge shared library. Download from cmake.org.
  • Development Tools: A C++17-compatible compiler is required.
    • Windows: Visual Studio (MSVC) or mingw-w64 GCC.
    • Linux: The default g++ from your distribution.
    • macOS: Xcode Command Line Tools (xcode-select --install).

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/v2

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

module github.com/yushulx/goBarcodeQrSDK/v2

go 1.24.0

Step 2: Preparing the Dynamsoft SDK and Building the C++ Bridge

The Dynamsoft Capture Vision SDK is compatible with multiple platforms, including Windows (x64), Linux (x64, ARM64), and macOS (x64, ARM64). This guide covers Windows x64, Linux x64, and macOS configurations.

2.1 Organising the SDK Files

Within the goBarcodeQrSDK directory, create a dcv folder. Copy the SDK’s shared libraries and header files into it following this layout:

|- goBarcodeQrSDK
    |- dcv
        |- include
            |- bridge.h                        (custom C bridge header)
            |- DynamsoftBarcodeReader.h
            |- DynamsoftCaptureVisionRouter.h
            |- DynamsoftCore.h
            |- DynamsoftLicense.h
            |- DynamsoftUtility.h
            |- (other DCV headers)
        |- lib
            |- win
                |- DynamsoftBarcodeReaderx64.dll
                |- DynamsoftCaptureVisionRouterx64.dll
                |- DynamsoftCorex64.dll
                |- DynamsoftLicensex64.dll
                |- DynamsoftUtilityx64.dll
                |- Models\*.data                (decoder model files)
            |- linux
                |- libDynamsoftBarcodeReader.so
                |- libDynamsoftCaptureVisionRouter.so
                |- libDynamsoftCore.so
                |- libDynamsoftLicense.so
                |- libDynamsoftUtility.so
                |- Models\*.data
            |- mac
                |- libDynamsoftBarcodeReader.dylib
                |- libDynamsoftCaptureVisionRouter.dylib
                |- libDynamsoftCore.dylib
                |- libDynamsoftLicense.dylib
                |- libDynamsoftUtility.dylib
                |- Models\*.data
        |- resource
            |- Templates\DBR-PresetTemplates.json
            |- Models\*.data
            |- ParserResources\*.data

2.2 Creating the C Bridge Header (dcv/include/bridge.h)

Because Go’s CGo can interface with C but not directly with C++, we define a pure-C interface in bridge.h that the C++ implementation will expose via extern "C":

#pragma once

#ifdef __cplusplus
extern "C"
{
#endif

#define DBR_OK    0
#define DBR_ERROR -1

typedef struct
{
    char *text;
    char *format;
    int x1, y1, x2, y2, x3, y3, x4, y4;
    int pageId; // Page ID for multi-page documents (PDF, TIFF)
} BarcodeResultC;

typedef struct
{
    BarcodeResultC *results;
    int count;
} BarcodeResultArrayC;

// Core functions
int         DBR_InitLicense(const char *license, char *errorMsg, int errorMsgBufferLen);
void       *DBR_CreateInstance();
void        DBR_DestroyInstance(void *instance);
const char *DBR_GetVersion();

// Settings functions
int DBR_InitRuntimeSettingsWithString(void *instance, const char *content, int conflictMode, char *errorMsg, int errorMsgBufferLen);
int DBR_InitRuntimeSettingsWithFile  (void *instance, const char *fileName, int conflictMode, char *errorMsg, int errorMsgBufferLen);

// Decoding functions
int  DBR_DecodeFile        (void *instance, const char *fileName, BarcodeResultArrayC **results, char *errorMsg, int errorMsgBufferLen);
int  DBR_DecodeFileInMemory(void *instance, const unsigned char *buffer, int bufferLen, BarcodeResultArrayC **results, char *errorMsg, int errorMsgBufferLen);
void DBR_FreeTextResults   (BarcodeResultArrayC **results);

#ifdef __cplusplus
}
#endif

2.3 Creating the C++ Bridge Implementation (src/bridge.cpp)

The implementation wraps the Dynamsoft Capture Vision C++ API and exposes it through the C bridge defined above. The key class is CCaptureVisionRouter, which replaces the older CBarcodeReader from v9.x:

#include "../dcv/include/bridge.h"
#include "DynamsoftCaptureVisionRouter.h"
#include "DynamsoftLicense.h"
#include "DynamsoftBarcodeReader.h"
#include "DynamsoftUtility.h"
#include <string>
#include <vector>

using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
using namespace dynamsoft::basic_structures;

struct BarcodeReaderInstance
{
    CCaptureVisionRouter *router;
    CCapturedResultArray *lastResultArray;
    std::vector<BarcodeResultC> results;
    std::vector<std::string> textStrings;
    std::vector<std::string> formatStrings;

    BarcodeReaderInstance() : router(nullptr), lastResultArray(nullptr) {}
};

extern "C"
{
    int DBR_InitLicense(const char *license, char *errorMsg, int errorMsgSize)
    {
        return CLicenseManager::InitLicense(license, errorMsg, errorMsgSize);
    }

    void *DBR_CreateInstance()
    {
        BarcodeReaderInstance *instance = new BarcodeReaderInstance();
        instance->router = new CCaptureVisionRouter();
        return instance;
    }

    void DBR_DestroyInstance(void *instance)
    {
        if (instance)
        {
            BarcodeReaderInstance *inst = static_cast<BarcodeReaderInstance *>(instance);
            if (inst->lastResultArray) inst->lastResultArray->Release();
            delete inst->router;
            delete inst;
        }
    }

    const char *DBR_GetVersion()
    {
        return CBarcodeReaderModule::GetVersion();
    }

    int DBR_InitRuntimeSettingsWithString(void *instance, const char *content, int conflictMode,
                                          char *errorMsg, int errorMsgSize)
    {
        BarcodeReaderInstance *inst = static_cast<BarcodeReaderInstance *>(instance);
        return inst->router->InitSettings(content, errorMsg, errorMsgSize);
    }

    int DBR_InitRuntimeSettingsWithFile(void *instance, const char *fileName, int conflictMode,
                                        char *errorMsg, int errorMsgSize)
    {
        BarcodeReaderInstance *inst = static_cast<BarcodeReaderInstance *>(instance);
        return inst->router->InitSettingsFromFile(fileName, errorMsg, errorMsgSize);
    }

    // DBR_DecodeFile and DBR_DecodeFileInMemory implementations use
    // inst->router->CaptureMultiPages() and iterate CCapturedResultArray
    // to populate BarcodeResultArrayC (see full source for details).
} // extern "C"

2.4 Building the Bridge Shared Library with CMake

A CMakeLists.txt at the project root compiles src/bridge.cpp into a shared library called bridge and places it next to the Dynamsoft SDK libraries:

cmake_minimum_required(VERSION 3.10)
project(bridge)

set(CMAKE_CXX_STANDARD 17)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dcv/include)

if(WIN32)
    link_directories(${CMAKE_CURRENT_SOURCE_DIR}/dcv/lib/win)
    set(DYNAMSOFT_LIBS
        DynamsoftBarcodeReaderx64
        DynamsoftCaptureVisionRouterx64
        DynamsoftCorex64
        DynamsoftLicensex64
        DynamsoftUtilityx64
    )
elseif(APPLE)
    link_directories(${CMAKE_CURRENT_SOURCE_DIR}/dcv/lib/mac)
    set(DYNAMSOFT_LIBS
        DynamsoftBarcodeReader
        DynamsoftCaptureVisionRouter
        DynamsoftCore
        DynamsoftLicense
        DynamsoftUtility
    )
else()
    link_directories(${CMAKE_CURRENT_SOURCE_DIR}/dcv/lib/linux)
    set(DYNAMSOFT_LIBS
        DynamsoftBarcodeReader
        DynamsoftCaptureVisionRouter
        DynamsoftCore
        DynamsoftLicense
        DynamsoftUtility
    )
endif()

add_library(bridge SHARED src/bridge.cpp)
target_link_libraries(bridge ${DYNAMSOFT_LIBS})

Build the library:

# Linux / macOS
cmake -B build .
cmake --build build

# Windows (Visual Studio)
cmake -B build .
cmake --build build --config Release

After a successful build, bridge.dll (Windows), libbridge.so (Linux), or libbridge.dylib (macOS) will appear inside the corresponding dcv/lib/<platform> directory alongside the Dynamsoft SDK libraries.

Step 3: Writing the CGo Wrapper for the Dynamsoft Capture Vision SDK

CGo enables Go programs to call C code directly, facilitating the integration of C/C++ libraries. Create a file named reader.go inside the goBarcodeQrSDK directory.

3.1 CGo Build Directives

package goBarcodeQrSDK

import (
    "errors"
    "strings"
    "unsafe"

    /*
       #cgo CFLAGS: -I${SRCDIR}/dcv/include
       #cgo darwin  LDFLAGS: -L${SRCDIR}/dcv/lib/mac   -lbridge -Wl,-rpath,${SRCDIR}/dcv/lib/mac
       #cgo linux   LDFLAGS: -L${SRCDIR}/dcv/lib/linux -lbridge -Wl,-rpath,${SRCDIR}/dcv/lib/linux
       #cgo windows LDFLAGS: -L${SRCDIR}/dcv/lib/win   -lbridge
       #include <stdlib.h>
       #include "bridge.h"
    */
    "C"
)

The CGo linker directives reference only the bridge shared library. The bridge library itself depends on the Dynamsoft Capture Vision DLLs, so the OS will load them transitively at runtime.

3.2 The Barcode Struct

type Barcode struct {
    Text   string
    Format string
    X1     int
    Y1     int
    X2     int
    Y2     int
    X3     int
    Y3     int
    X4     int
    Y4     int
    PageId int // Page ID for multi-page documents (PDF, TIFF)
}

The PageId field tracks which page of a multi-page PDF or TIFF a barcode was found on.

3.3 License Initialisation

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_InitLicense delegates to CLicenseManager::InitLicense in the DCV SDK.

3.4 Creating and Destroying a Reader Instance

type BarcodeReader struct {
    handler unsafe.Pointer
}

func CreateBarcodeReader() *BarcodeReader {
    handler := C.DBR_CreateInstance()
    if handler == nil {
        return nil
    }
    return &BarcodeReader{handler: handler}
}

func DestroyBarcodeReader(obj *BarcodeReader) {
    C.DBR_DestroyInstance(obj.handler)
}

Internally, DBR_CreateInstance allocates a CCaptureVisionRouter instance, which is the primary entry point of the DCV SDK.

3.5 Configuring the Barcode Reader

Two helpers load scanning parameters. The DCV SDK uses a JSON-based template format (see template.json in the repository):

// SetParameters applies a JSON template string.
func (reader *BarcodeReader) SetParameters(params string) (int, string) {
    errorBuffer := make([]byte, 256)
    c_params := C.CString(params)
    defer C.free(unsafe.Pointer(c_params))

    ret := C.DBR_InitRuntimeSettingsWithString(reader.handler, c_params,
        C.int(2),
        (*C.char)(unsafe.Pointer(&errorBuffer[0])),
        C.int(len(errorBuffer)))
    return int(ret), string(errorBuffer)
}

// LoadTemplateFile applies a JSON template from a file path.
func (reader *BarcodeReader) LoadTemplateFile(params string) (int, string) {
    errorBuffer := make([]byte, 256)
    c_params := C.CString(params)
    defer C.free(unsafe.Pointer(c_params))

    ret := C.DBR_InitRuntimeSettingsWithFile(reader.handler, c_params,
        C.int(2),
        (*C.char)(unsafe.Pointer(&errorBuffer[0])),
        C.int(len(errorBuffer)))
    return int(ret), string(errorBuffer)
}

3.6 Decoding Barcodes from a File

DecodeFile now returns ([]Barcode, error) instead of the old (int, []Barcode). Internally it calls CaptureMultiPages, which handles standard image files as well as multi-page PDFs and TIFFs in a single call:

func (reader *BarcodeReader) DecodeFile(filePath string) ([]Barcode, error) {
    c_filePath := C.CString(filePath)
    defer C.free(unsafe.Pointer(c_filePath))

    var resultArray *C.BarcodeResultArrayC
    errorBuffer := make([]byte, 256)
    ret := C.DBR_DecodeFile(reader.handler, c_filePath, &resultArray,
        (*C.char)(unsafe.Pointer(&errorBuffer[0])),
        C.int(len(errorBuffer)))

    barcodes := reader.processResults(resultArray)

    errorMsg := strings.TrimRight(string(errorBuffer), "\x00")
    if ret != 0 && len(barcodes) == 0 {
        if errorMsg == "" {
            errorMsg = "Decoding failed"
        }
        return barcodes, errors.New(errorMsg)
    }
    return barcodes, nil
}

3.7 Decoding Barcodes from a Memory Buffer

A new DecodeStream function reads barcodes directly from a byte slice, useful when the image data is already in memory:

func (reader *BarcodeReader) DecodeStream(data []byte) ([]Barcode, error) {
    if len(data) == 0 {
        return []Barcode{}, errors.New("empty data buffer")
    }

    cData := (*C.uchar)(unsafe.Pointer(&data[0]))
    length := C.int(len(data))

    var resultArray *C.BarcodeResultArrayC
    errorBuffer := make([]byte, 256)
    ret := C.DBR_DecodeFileInMemory(reader.handler, cData, length, &resultArray,
        (*C.char)(unsafe.Pointer(&errorBuffer[0])),
        C.int(len(errorBuffer)))

    barcodes := reader.processResults(resultArray)

    errorMsg := strings.TrimRight(string(errorBuffer), "\x00")
    if ret != 0 && len(barcodes) == 0 {
        if errorMsg == "" {
            errorMsg = "Decoding failed"
        }
        return barcodes, errors.New(errorMsg)
    }
    return barcodes, nil
}

3.8 SDK Version Query

func GetVersion() string {
    version := C.DBR_GetVersion()
    return C.GoString(version)
}

Step 4: Building and Testing the Go Module

Now that you have set up the CGo wrapper alongside the barcode reading functions, compile and test the module using a _test.go file.

package goBarcodeQrSDK

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

func TestInitLicense(t *testing.T) {
    ret, _ := InitLicense("LICENSE-KEY")
    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 TestDestroyBarcodeReader(t *testing.T) {
    obj := CreateBarcodeReader()
    DestroyBarcodeReader(obj)
}

func TestSetParameters(t *testing.T) {
    obj := CreateBarcodeReader()

    templateData, err := os.ReadFile("template.json")
    if err != nil {
        t.Fatalf("Failed to read template.json: %v", err)
    }

    ret, _ := obj.SetParameters(string(templateData))
    if ret != 0 {
        t.Fatalf(`SetParameters() = %d`, ret)
    }
}

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

    templateData, err := os.ReadFile("template.json")
    if err != nil {
        t.Fatalf("Failed to read template.json: %v", err)
    }

    obj.SetParameters(string(templateData))
    _, err = obj.DecodeFile("test.png")
    if err != nil {
        t.Fatalf(`DecodeFile() failed: %v`, err)
    }
}

func TestApp(t *testing.T) {
    ret, _ := InitLicense("LICENSE-KEY")
    if ret != 0 {
        t.Fatalf(`InitLicense() = %d`, ret)
    }
    obj := CreateBarcodeReader()

    templateData, err := os.ReadFile("template.json")
    if err != nil {
        t.Fatalf("Failed to read template.json: %v", err)
    }

    obj.SetParameters(string(templateData))
    startTime := time.Now()
    barcodes, err := obj.DecodeFile("test.png")
    elapsed := time.Since(startTime)
    fmt.Println("DecodeFile() time cost:", elapsed)

    if err != nil {
        t.Fatalf(`DecodeFile() failed: %v`, err)
    }

    for _, barcode := range barcodes {
        fmt.Printf("Page %d | %s | %s\n", barcode.PageId, barcode.Format, barcode.Text)
    }
}

Running go test directly may fail because the OS cannot find the shared libraries at runtime:

exit status 0xc0000135   # Windows

Use the provided scripts to configure the library search path before running the tests.

  • PowerShell script for Windows (run_windows_test.ps1):

      $originalPath = $env:PATH
    
      $dllPath = "dcv\lib\win"
      $env:PATH = "$dllPath;$originalPath"
    
      go test
    
      $env:PATH = $originalPath
    
  • Shell script for Linux (run_linux_test.sh):

      #!/bin/bash
      ORIGINAL_LD_LIBRARY_PATH=$LD_LIBRARY_PATH
      export LD_LIBRARY_PATH="dcv/lib/linux:$LD_LIBRARY_PATH"
    
      go test
    
      export LD_LIBRARY_PATH=$ORIGINAL_LD_LIBRARY_PATH
    
  • Shell script for macOS (run_mac_test.sh):

      #!/bin/bash
      ORIGINAL_DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH
      export DYLD_LIBRARY_PATH="dcv/lib/mac:$DYLD_LIBRARY_PATH"
    
      go test
    
      export DYLD_LIBRARY_PATH=$ORIGINAL_DYLD_LIBRARY_PATH
    

After running these scripts, you should see all tests pass.

Step 5: Implementing a Go Barcode and QR Code Reader

Create a test.go file inside example/command-line and add the following code:

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/yushulx/goBarcodeQrSDK/v2"
)

func main() {
    version := goBarcodeQrSDK.GetVersion()
    fmt.Println("SDK version:", version)

    filename := "test.png"
    license := "LICENSE-KEY"
    template := "template.json"

    if len(os.Args) > 1 {
        for i := 1; i < len(os.Args); i++ {
            switch i {
            case 1:
                if _, err := os.Stat(os.Args[1]); err == nil {
                    filename = os.Args[1]
                } else {
                    fmt.Println("Input file not found, using test.png instead.")
                }
            case 2:
                license = os.Args[2]
            case 3:
                if _, err := os.Stat(os.Args[3]); err == nil {
                    template = os.Args[3]
                } else {
                    fmt.Println("Template file not found, using template.json instead.")
                }
            }
        }
    } else {
        fmt.Println("Usage: reader [image] [license] [template]")
        return
    }

    ret, errMsg := goBarcodeQrSDK.InitLicense(license)
    if ret != 0 {
        fmt.Println("InitLicense():", ret, errMsg)
        return
    }

    obj := goBarcodeQrSDK.CreateBarcodeReader()
    ret, errMsg = obj.LoadTemplateFile(template)
    if ret != 0 {
        fmt.Println("LoadTemplateFile():", ret, errMsg)
    }

    startTime := time.Now()
    barcodes, err := obj.DecodeFile(filename)
    elapsed := time.Since(startTime)
    fmt.Println("DecodeFile() time cost:", elapsed)

    if err != nil {
        fmt.Printf("DecodeFile() failed: %v\n", err)
        return
    }

    for i, barcode := range barcodes {
        fmt.Printf("--- Barcode %d (Page %d) ---\n", i+1, barcode.PageId)
        fmt.Println("Text:  ", barcode.Text)
        fmt.Println("Format:", barcode.Format)
        fmt.Printf("Location: (%d,%d) (%d,%d) (%d,%d) (%d,%d)\n",
            barcode.X1, barcode.Y1, barcode.X2, barcode.Y2,
            barcode.X3, barcode.Y3, barcode.X4, barcode.Y4)
    }
}

Remember to substitute LICENSE-KEY with your own license key.

Utilise the provided scripts to load the necessary libraries and run the program on each platform.

  • PowerShell script for Windows (example/command-line/run_windows.ps1):

      $originalPath = $env:PATH
    
      $LIBRARY_PATH = "../../dcv/lib/win"
      Write-Host "LIBRARY_PATH set to $LIBRARY_PATH"
      $env:PATH = "$LIBRARY_PATH;" + $env:PATH
    
      go run test.go test.png
    
      $env:PATH = $originalPath
    

    cgo barcode reader on Windows

  • Shell script for Linux (example/command-line/run_linux.sh):

      #!/bin/bash
      originalPath=$LD_LIBRARY_PATH
    
      LIBRARY_PATH="../../dcv/lib/linux"
      echo "LIBRARY_PATH set to $LIBRARY_PATH"
      export LD_LIBRARY_PATH="$LIBRARY_PATH:$originalPath"
    
      go run test.go test.png
    
      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:

     # Build stage
     FROM golang:1.24 AS builder
     COPY . /usr/src/myapp
     WORKDIR /usr/src/myapp/example/command-line
     # Copy Dynamsoft shared libraries into system library path so the
     # built goBarcodeQrSDK binaries can find them at runtime
     RUN cp -r ../../dcv/lib/linux/* /usr/lib/x86_64-linux-gnu/
     RUN cp ../../template.json /usr/local/bin/
     RUN go mod download
     RUN CGO_ENABLED=1 go build -v -o /usr/local/bin/reader .
    
     # Runtime stage
     FROM debian:bookworm-slim
     RUN apt-get update && apt-get install -y --no-install-recommends \
         libc6 libstdc++6 libgomp1 ca-certificates \
         && rm -rf /var/lib/apt/lists/*
     COPY --from=builder /usr/local/bin/reader /usr/local/bin/reader
     COPY --from=builder /usr/src/myapp/example/command-line/template.json /usr/local/bin/template.json
     # Dynamsoft SDK needs its model files
     COPY --from=builder /usr/src/myapp/dcv/lib/linux/Models /usr/local/share/dcv/Models
     WORKDIR /usr/local/bin
     ENTRYPOINT ["reader"]
    

    Three things are essential in this Dockerfile:

    • dcv/lib/linux/* (not the old lib/linux) is copied into /usr/lib/x86_64-linux-gnu/ so the Linux .so files load at runtime.
    • The golang:1.24 builder produces a reader binary compiled with CGO_ENABLED=1.
    • The multi-stage runtime image ships the Models/*.data files the SDK needs for accurate decoding.
  2. Build your Docker image:

     docker build -t golang-barcode-qr-reader .
    
  3. Run the container, mounting a local folder containing the image to scan:

     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>

Common Issues & Edge Cases

  • exit status 0xc0000135 on Windows — the Dynamsoft DLLs are not on PATH. Run run_windows_test.ps1 or add dcv\lib\win to PATH before go run.
  • go: error loading shared libraries: libDynamsoftBarcodeReader.so on Linux — set LD_LIBRARY_PATH=dcv/lib/linux (see run_linux_test.sh), or install the .so files into /usr/lib/x86_64-linux-gnu/.
  • dyld: Library not loaded on macOS — add the @rpath with install_name_tool -add_rpath dcv/lib/mac <binary> as run_mac_test.sh does; DYLD_LIBRARY_PATH can be ignored on recent macOS.
  • License initialization fails — the license string in the examples is a sample; replace it with your own trial or commercial key before distributing your app.
  • Multi-page PDFs — pass the file directly to DecodeFile; the module renders each page with PDFium and sets PageId on each result. Larger PDFs take longer because every page is rendered and decoded.

Source Code

Get the complete sample project source code on GitHub

Disclaimer:

The wrappers and sample code on Dynamsoft Codepool are community editions, shared as-is and not fully tested. Dynamsoft is happy to provide technical support for users exploring these solutions but makes no guarantees.