DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

3 1

How to Build a Command-Line Barcode Reader with Rust and C++ Barcode SDK

Rust's popularity is increasing rapidly. This article aims to integrate Rust with the Dynamsoft C++ Barcode Reader SDK. We will walk through the process of building a command-line barcode reader for Windows and Linux.

Prerequisites

  • Rust: A systems programming language renowned for speed, safety, and concurrency.
  • bindgen: A Rust tool that generates Rust FFI bindings to C and C++ libraries. You can install it with the following command:

    cargo install bindgen-cli
    
  • Dynamsoft Barcode Reader Trial License: You will receive a 30-day free trial license by email.

  • Dynamsoft C++ Barcode SDK v9.x: A ZIP package that contains the shared library and header files for Windows and Linux.

Step 1: Setting Up the Rust Project

  1. Use Cargo to initialize the project:

    cargo new barcode_reader
    cd barcode_reader
    
  2. Modify Cargo.toml to include the required dependencies:

    [package]
    name = "hello_world"
    version = "0.1.0"
    edition = "2018"
    
    [build-dependencies]
    cc = "1.0"
    walkdir = "2.5.0"
    

    The cc crate is used to compile the C++ code. The walkdir crate is used to traverse the directory to find the shared library.

Step 2: Configuring the C++ Barcode SDK

  1. Extract the downloaded Dynamsoft C++ Barcode SDK, and copy the headers and platform-specific libraries to the Rust project directory structure as follows:

    |- include
        |- DynamsoftBarcodeReader.h
        |- DynamsoftCommon.h
    |- platforms
        |- linux
            |- libDynamicPdf.so
            |- libDynamsoftLicenseClient.so
            |- libDynamsoftBarcodeReader.so
        |- win
            |- bin
                |- DynamicPdfx64.dll
                |- DynamsoftBarcodeReaderx64.dll
                |- DynamsoftLicenseClientx64.dll
                |- vcomp110.dll
            |- lib
                |- DBRx64.lib
    
    
  2. Create a lib directory within your project. In the lib folder, create two files: bridge.cpp and bridge.h. These files will handle communication between Rust and the C++ SDK.

  3. Edit build.rs to build the C++ code and link the shared libraries.

    1. Determine the target operating system (Windows/Linux). When running cargo build, the println!() function won't output anything to the console unless you add cargo:warning to the message.

      use std::env;
      use cc::Build;
      
      use std::fs;
      use walkdir::WalkDir;
      use std::path::{Path, PathBuf};
      
      fn main() {
          // Determine the target operating system
          let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
          println!("cargo:warning=OS: {}..............................................", target_os);
      }
      
    2. Link the shared libraries based on the target operating system, and copy the shared libraries to the output path.

      fn get_out_dir() -> PathBuf {
          let out_dir = env::var("OUT_DIR").unwrap();
          let debug_offset = out_dir.find("debug").unwrap_or(0);
          let release_offset = out_dir.find("release").unwrap_or(0);
          let mut path = String::from("");
      
          if debug_offset > 0 {
              println!(">>> where is debug {}", debug_offset);
              path.push_str(&format!("{}", &out_dir[..debug_offset]));
              path.push_str("debug");
              println!("{}", path);
          }
      
          if release_offset > 0 {
              println!(">>> where is release {}", release_offset);
              path.push_str(&format!("{}", &out_dir[..release_offset]));
              path.push_str("release");
              println!("{}", path);
          }
      
          PathBuf::from(path)
      }
      
      fn copy_shared_libs_from_dir_to_out_dir(src_dir: &Path, out_dir: &Path, extension: &str) {
          for entry in WalkDir::new(src_dir).into_iter().filter_map(|e| e.ok()) {
              if entry.path().extension().and_then(|ext| ext.to_str()) == Some(extension) {
                  let lib_path = entry.path();
                  let file_name = lib_path.file_name().unwrap();
                  let dest_path = out_dir.join(file_name);
      
                  fs::copy(lib_path, dest_path.clone()).expect("Failed to copy shared library");
                  println!("Copied {} to {}", lib_path.display(), dest_path.display());
              }
          }
      }
      
      match target_os.as_str() {
          "windows" => {
              // Link Dynamsoft Barcode Reader for Windows
              println!("cargo:rustc-link-search=../../../platforms/win/lib");
              println!("cargo:rustc-link-lib=static=DBRx64");
      
              // Copy *.dll files to the output path for Windows
              let src_dir = Path::new("../../../platforms/win/bin");
              copy_shared_libs_from_dir_to_out_dir(src_dir, &get_out_dir(), "dll");
          },
          "linux" => {
              // Link Dynamsoft Barcode Reader for Linux
              println!("cargo:rustc-link-search=../../../platforms/linux");
              println!("cargo:rustc-link-lib=dylib=DynamsoftBarcodeReader");
      
              // Set rpath for Linux
              println!("cargo:rustc-link-arg=-Wl,-rpath,../../../platforms/linux");
      
              // Copy *.so files to the output path for Linux
              let src_dir = Path::new("../../../platforms/linux/bin");
              copy_shared_libs_from_dir_to_out_dir(src_dir, &get_out_dir(), "so");
          },
      }
      
    3. Compile the C++ code that exposes some C functions to Rust.

      Build::new()
      .cpp(true)
      .include("../../../include")
      .file("lib/bridge.cpp")
      .compile("bridge");
      
      println!("cargo:rustc-link-lib=static=bridge");
      
      println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap());
      

Step3: Implementing the C/C++ Bridging Code

In this step, we will create the bridging code to enable Rust to interact with the C++ SDK. We will declare and implement the necessary structures and functions in C/C++.

Declaring Structures and Functions in bridge.h

In the directory, create a file named bridge.h and declare the C structures and functions that will be called by Rust.

#ifndef BRIDGE_H
#define BRIDGE_H

#include "DynamsoftBarcodeReader.h"

#ifdef __cplusplus
extern "C"
{
#endif

    typedef struct
    {
        const char *barcode_type;
        const char *barcode_value;
        int x1;
        int y1;
        int x2;
        int y2;
        int x3;
        int y3;
        int x4;
        int y4;
    } Barcode;

    typedef struct
    {
        Barcode *barcodes;
        int count;
    } BarcodeResults;

    Barcode *create_barcode(const char *type, const char *value, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
    BarcodeResults *decode_barcode_file(void *instance, const char *filename);
    void free_barcode(BarcodeResults *results);
    int init_license(const char *license);

#ifdef __cplusplus
}
#endif

#endif // BRIDGE_H

Enter fullscreen mode Exit fullscreen mode
  • The Barcode structure represents the barcode information.
  • The BarcodeResults structure contains an array of Barcode structures.
  • The create_barcode function creates a Barcode structure.
  • The decode_barcode_file function decodes barcodes from an image file.
  • The free_barcode function releases the memory allocated for the BarcodeResults structure.
  • The init_license function initializes the license.

Implementing the Functions in bridge.cpp

In the bridge.cpp file, implement the functions declared in bridge.h.

#include "bridge.h"
#include <cstring>
#include <cstdlib>

Barcode *create_barcode(const char *type, const char *value, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
    Barcode *barcode = (Barcode *)std::malloc(sizeof(Barcode));
    barcode->barcode_type = strdup(type);
    barcode->barcode_value = strdup(value);
    barcode->x1 = x1;
    barcode->y1 = y1;
    barcode->x2 = x2;
    barcode->y2 = y2;
    barcode->x3 = x3;
    barcode->y3 = y3;
    barcode->x4 = x4;
    barcode->y4 = y4;
    return barcode;
}

void free_barcode(BarcodeResults *results)
{
    for (int i = 0; i < results->count; i++)
    {
        std::free((void *)results->barcodes[i].barcode_type);
        std::free((void *)results->barcodes[i].barcode_value);
    }
    std::free(results->barcodes);
    std::free(results);
}

int init_license(const char *license)
{
    char errorMsgBuffer[512];
    // Click https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr to get a trial license.
    int ret = DBR_InitLicense(license, errorMsgBuffer, 512);
    return ret;
}

BarcodeResults *decode_barcode_file(void *instance, const char *filename)
{
    char errorMsgBuffer[512];
    TextResultArray *pResults = NULL;
    BarcodeResults *all_barcodes = NULL;
    int ret = DBR_DecodeFile(instance, filename, "");
    DBR_GetAllTextResults(instance, &pResults);
    if (pResults->resultsCount > 0)
    {
        all_barcodes = (BarcodeResults *)std::malloc(sizeof(BarcodeResults));
        all_barcodes->count = pResults->resultsCount;
        all_barcodes->barcodes = (Barcode *)std::malloc(sizeof(Barcode) * pResults->resultsCount);
        for (int iIndex = 0; iIndex < pResults->resultsCount; iIndex++)
        {
            LocalizationResult *localizationResult = pResults->results[iIndex]->localizationResult;
            Barcode *barcode = create_barcode(pResults->results[iIndex]->barcodeFormatString, pResults->results[iIndex]->barcodeText,
                                              localizationResult->x1, localizationResult->y1, localizationResult->x2, localizationResult->y2,
                                              localizationResult->x3, localizationResult->y3, localizationResult->x4, localizationResult->y4);
            all_barcodes->barcodes[iIndex] = *barcode;
        }
    }

    DBR_FreeTextResults(&pResults);
    return all_barcodes;
}
Enter fullscreen mode Exit fullscreen mode

Step4: Generating Rust Bindings for C/C++ Code

To invoke the C/C++ functions from Rust, we need to generate Rust bindings for the C/C++ code. We can either write the bindings manually or use the bindgen tool to generate them automatically as follows:

bindgen ./lib/bridge.h -o bindings.rs
Enter fullscreen mode Exit fullscreen mode

In addition to the methods implemented in bridge.cpp, we add two more functions contained in the C++ SDK: DBR_CreateInstance and DBR_DestroyInstance. The full bindings.rs file is as follows:

use std::ffi::c_void;
use std::os::raw::c_char;
use std::os::raw::c_int;

#[repr(C)]
pub struct Barcode {
    pub barcode_type: *const c_char,
    pub barcode_value: *const c_char,
    pub x1: c_int,
    pub y1: c_int,
    pub x2: c_int,
    pub y2: c_int,
    pub x3: c_int,
    pub y3: c_int,
    pub x4: c_int,
    pub y4: c_int,
}

#[repr(C)]
pub struct BarcodeResults {
    pub barcodes: *mut Barcode,
    pub count: c_int,
}

extern "C" {
    // Bridge functions
    pub fn free_barcode(barcode: *mut BarcodeResults);
    pub fn init_license(license: *const c_char) -> c_int;
    pub fn decode_barcode_file(instance: *mut c_void, filename: *const c_char) -> *mut BarcodeResults;

    // Dynamsoft C++ Barcode Reader SDK functions
    pub fn DBR_CreateInstance() -> *mut c_void;
    pub fn DBR_DestroyInstance(barcodeReader: *mut c_void); 
}

Enter fullscreen mode Exit fullscreen mode

Step5: Writing Rust Code

The final step is to write Rust code in the main.rs file to implement the command-line barcode reader.

  1. Import the generated bindings and other necessary libraries.

    mod bindings;
    use std::io::{self, Write};
    use std::ffi::CString;
    use bindings::*;
    
  2. Activate the license of Dynamsoft Barcode Reader:

    let license = "LICENSE-KEY";
    
    let ret = unsafe {
        let license = CString::new(license).expect("CString::new failed");
        init_license(license.as_ptr())
    };
    
    println!("InitLicense: {}", ret);
    
  3. Create an instance of Dynamsoft Barcode Reader:

    let reader_ptr = unsafe { DBR_CreateInstance() };
    if reader_ptr.is_null() {
        panic!("Failed to create barcode reader instance");
    }
    
  4. Prompt the user to enter a file name in a loop. If the user types exit, the program will exit.

    loop {
        print!("Please enter the file name (or type 'exit' to quit): ");
        io::stdout().flush().unwrap(); 
    
        let mut file_name = String::new();
        io::stdin().read_line(&mut file_name).expect("Failed to read line");
    
        let file_name = file_name.trim();
    
        if file_name.to_lowercase() == "exit" {
            break;
        }
    
        println!("Processing file: {}", file_name);
    
        let path = CString::new(file_name).expect("CString::new failed");
    }
    
  5. Decode barcodes from the image file and print the results.

    unsafe {
        let results_ptr = decode_barcode_file(reader_ptr, path.as_ptr());
    
        if results_ptr.is_null() {
            println!("No barcodes found.");
        } else {
            let results = &*results_ptr;
            let barcodes = std::slice::from_raw_parts(results.barcodes, results.count as usize);
    
            for (i, barcode) in barcodes.iter().enumerate() {
                let barcode_type = std::ffi::CStr::from_ptr(barcode.barcode_type).to_string_lossy();
                let barcode_value = std::ffi::CStr::from_ptr(barcode.barcode_value).to_string_lossy();
    
                println!("Barcode {}: type = {}, value = {}", i + 1, barcode_type, barcode_value);
                println!(
                    "Coordinates: ({}, {}), ({}, {}), ({}, {}), ({}, {})",
                    barcode.x1, barcode.y1, barcode.x2, barcode.y2,
                    barcode.x3, barcode.y3, barcode.x4, barcode.y4
                );
            }
    
            free_barcode(results_ptr);
        }
    }
    
  6. Run the program.

    cargo clean
    cargo run
    

    Rust barcode reader

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/9.x/rust

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay