How to Build a Go Barcode Reader on Apple Silicon Macs (macOS dylib Linking)

The goBarcodeQrSDK Go module reads barcodes and QR codes on macOS by using cgo to link a C bridge library against the Dynamsoft Capture Vision SDK (Barcode Reader 11.6.10) universal *.dylib files. The Dynamsoft macOS libraries now ship as universal binaries — one libDynamsoftBarcodeReader.dylib works on both Apple Silicon (arm64) and Intel (x86_64) Macs — so you download the SDK once and link the same files on every Mac.

What you’ll build: a Go module plus command-line reader that decodes QR codes, Data Matrix, PDF417, and other 1D/2D symbols from images and PDFs on an Apple Silicon Mac, with the correct @rpath/DYLD_LIBRARY_PATH handling so the dylibs actually load.

Key Takeaways

  • The Dynamsoft DCV SDK ships universal macOS dylibs (arm64 + x86_64); the goBarcodeQrSDK repo stores them under dcv/lib/mac/.
  • cgo cannot call C++ directly, so the module links a C bridge (libbridge.dylib) that wraps CCaptureVisionRouter with extern "C".
  • On macOS, add the dylib directory with -Wl,-rpath,${SRCDIR}/dcv/lib/mac in the cgo LDFLAGS so dyld resolves @rpath entries at runtime.
  • When rpath is not embedded, install_name_tool -add_rpath on the compiled binary is a reliable fallback that works on macOS Sonoma and later.
  • Build the bridge as a universal binary (lipo-merged arm64 + x86_64) so one libbridge.dylib serves all Macs.

Common Developer Questions

How do I load a macOS dylib from a Go program?

Add the library path to the cgo #cgo darwin LDFLAGS line with -Wl,-rpath,<path>, then link the library by name using the #cgo darwin LDFLAGS: -l<name> directive. The goBarcodeQrSDK module links -lbridge and points rpath at dcv/lib/mac, so dyld finds libbridge.dylib and its @rpath-referenced Dynamsoft dylibs automatically.

Does Dynamsoft Barcode Reader support Apple Silicon Macs?

Yes. Dynamsoft Barcode Reader 11.6.10 provides universal macOS binaries containing both arm64 (Apple Silicon) and x86_64 (Intel) code in a single libDynamsoftBarcodeReader.dylib. You can confirm with lipo -info — the file reports Architectures in the fat file with both arm64 and x86_64.

Why does my Go app crash with “dyld: Library not loaded”?

The macOS dynamic linker cannot find @rpath/libDynamsoftBarcodeReader.dylib when the compiled binary has no rpath entry pointing to dcv/lib/mac. Fix it by embedding the path at link time (-Wl,-rpath) or by running install_name_tool -add_rpath <path> <binary> on the built executable.

What is the universal bridge library on macOS?

The goBarcodeQrSDK module wraps the C++ CCaptureVisionRouter API in src/bridge.cpp and compiles it to libbridge.dylib. The scripts/build_macos_universal.sh script compiles the bridge once for arm64 and once for x86_64, then merges both with lipo -create so one dylib works on every Mac.

Development Environment

  • Mac mini (M2, 2023)
  • macOS Sonoma 14.2
  • Go 1.24
  • CMake

Apple Silicon Mac mini

Prerequisites

  • Xcode Command Line Tools

      xcode-select --install
    
  • Go Environment
  • Get a 30-day free trial license for Dynamsoft Barcode Reader.
  • CMake (brew install cmake)
  • The universal *.dylib files of Dynamsoft Barcode Reader (DCV) for macOS — the goBarcodeQrSDK repository already includes them under dcv/lib/mac/.

Unlike the older v9 SDK, the modern Capture Vision C++ SDK download ships the macOS libraries directly, including universal builds for Apple Silicon and Intel. No more extracting dylibs from the Python wheel — copy the contents of the dcv/lib/mac folder from the SDK (or the repo) and you are ready.

Step 1: Adding *.dylib Files to the Go Module

In the dcv/lib/mac directory of the module, keep the universal dynamic libraries from the SDK:

Add macOS dylib to Go module

The key files are libDynamsoftBarcodeReader.dylib, libDynamsoftCaptureVisionRouter.dylib, libDynamsoftCore.dylib, libDynamsoftLicense.dylib and libDynamsoftUtility.dylib. All of them are fat binaries (arm64 + x86_64), so a single set works on every Mac.

In reader.go, the macOS-specific cgo directives link the custom libbridge library and record the rpath:

package goBarcodeQrSDK

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

With these directives, a Go binary built for darwin embeds ${SRCDIR}/dcv/lib/mac as an LC_RPATH entry, and dyld resolves every @rpath/lib*.dylib dependency against it — no environment variables needed for normal builds.

Step 2: Building the C Bridge as a Universal dylib

The bridge translates the C++ CCaptureVisionRouter calls into the pure-C DBR_* functions declared in dcv/include/bridge.h. Build it with CMake:

# Single architecture (current Mac only)
./scripts/build_macos.sh

# Universal binary (arm64 + x86_64) — recommended for distribution
./scripts/build_macos_universal.sh

The universal script compiles the bridge in build_arm64 and build_x86_64 and merges the results:

lipo -create libbridge_arm64.dylib libbridge_x86_64.dylib -output dcv/lib/mac/libbridge.dylib

Verify the output:

lipo -info dcv/lib/mac/libbridge.dylib
# Architectures in the fat file: dcv/lib/mac/libbridge.dylib are: x86_64 arm64

Step 3: Ensuring Go Tests Pass on macOS

Execute go test in the terminal to initiate the tests.

go test error on macOS

If you see import errors such as:

  • permission denied
  • dyld: Library not loaded: libDynamsoftBarcodeReader.dylib

Resolving the Issues

  1. Permission Denied: This issue can often be resolved by running the test with sudo:

     sudo go test
    
  2. Dynamic Library Not Loaded: This error indicates that the dynamic linker cannot find libDynamsoftBarcodeReader.dylib. There are two reliable solutions:

    • Ensure the cgo LDFLAGS include -Wl,-rpath,${SRCDIR}/dcv/lib/mac (recommended — automated).

      ✔ rpath is embedded at build time with no manual steps.

    • Use the install_name_tool command to append the library search path to the test binary.

      ✔ A viable option when the rpath was not embedded.

    • Setting only DYLD_LIBRARY_PATH may be ignored on macOS Sonoma 14+ because of stricter runtime protections, so prefer rpath.

What Does the go test Command Do?

Go compiles the test files (*_test.go) together with the package into a temporary executable dedicated to tests. That binary must be able to resolve the dylibs, which is why the rpath matters:

go test -c -o testapp

./testapp

Adding Rpath to the Go Test Binary

  1. Inspect the library search path of the Go test binary.

     otool -L testapp
        
     testapp:
         @rpath/libDynamsoftBarcodeReader.dylib
         ...
    

    otool command

    @rpath is a placeholder that dyld resolves at runtime based on the LC_RPATH entries embedded in the executable.

  2. Append the library search path to the Go test binary.

     install_name_tool -add_rpath ./dcv/lib/mac testapp
    

    Verify the rpath was added:

     otool -l testapp | grep -A2 LC_RPATH
    

Writing a Script to Build and Run a Go Test

The repository’s run_mac_test.sh automates the compile-check-run cycle:

#!/bin/bash

RPATH="./dcv/lib/mac"
TARGET="testapp"

go test -c -o $TARGET

if ! otool -l $TARGET | grep -q $RPATH; then
    echo "Adding rpath $RPATH to $TARGET"
    install_name_tool -add_rpath $RPATH $TARGET
else
    echo "RPATH $RPATH already exists in $TARGET"
fi

./$TARGET

rm ./$TARGET

Run it:

chmod +x run_mac_test.sh

sudo ./run_mac_test.sh

go test on macOS

Step 4: Running the Go Barcode Reader Application on macOS

The command-line example lives in example/command-line. Its run_mac.sh script builds the app and adds the rpath for the module source directory:

#!/bin/bash

PACKAGE_PATH=$(find "$GOPATH/pkg/mod/github.com/yushulx" -mindepth 1 -maxdepth 1 -type d | sort -r | head -n 1)
RPATH="$PACKAGE_PATH/dcv/lib/mac"

TARGET="testapp"

go build -o $TARGET

if ! otool -l $TARGET | grep -q $RPATH; then
    echo "Adding rpath $RPATH to $TARGET"
    install_name_tool -add_rpath $RPATH $TARGET
else
    echo "RPATH $RPATH already exists in $TARGET"
fi

./$TARGET test.png

rm ./$TARGET

go build compiles the reader, install_name_tool appends the dylib search path, and the app decodes test.png on the Apple Silicon Mac.

Common Issues & Edge Cases

  • dyld: Library not loaded — the binary is missing the rpath; embed it at link time or add it with install_name_tool.
  • permission denied — rerun the script with sudo (the Dynamsoft SDK initializes some resources under privileged paths on macOS).
  • responds to different architectures from lipo — build the bridge for arm64 and x86_64 separately before merging; a single -arch build produces a thin binary that only runs on one Mac type.
  • DYLD_LIBRARY_PATH ignored on Sonoma — SIP hardened runtime behavior; prefer embedded rpath over environment variables.

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.