How to Build a Command-Line Barcode Reader with Rust and C++ Barcode SDK
Tags: rust, cpp, windows, linux
Published: 2024-06-06 07:38:03

**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](https://www.rust-lang.org/tools/install): 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:
    
    ```bash
    cargo install bindgen-cli
    ```

- [Dynamsoft Barcode Reader Trial License](https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr): You will receive a 30-day free trial license by email.
- [Dynamsoft C++ Barcode SDK v9.x](https://www.dynamsoft.com/barcode-reader/downloads/1000003-confirmation-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:

    ```bash
    cargo new barcode_reader
    cd barcode_reader
    ```

2. Modify `Cargo.toml` to include the required dependencies:

    ```toml
    [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:
    
    ```bash
    |- 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. 
    
        ```rust
        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.
    
        ```rust
        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.
    
        ```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.

```cpp
#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

```

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

```cpp
#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;
}
```

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

```bash
bindgen ./lib/bridge.h -o bindings.rs
```

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:

```rust
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); 
}
 
```


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

    ```rust
    mod bindings;
    use std::io::{self, Write};
    use std::ffi::CString;
    use bindings::*;
    ```

2. Activate the license of Dynamsoft Barcode Reader:
    
    ```rust
    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:

    ```rust
    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.
    
    ```rust
    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.

    ```rust
    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.

    ```bash
    cargo clean
    cargo run
    ```
    
    ![Rust barcode reader](https://www.dynamsoft.com/codepool/img/2024/06/rust-command-line-barcode-reader.jpg)

## Source Code
[https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/9.x/rust](https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/9.x/rust)