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
Use Cargo to initialize the project:
cargo new barcode_reader
cd barcode_reader
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
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
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.
Edit build.rs to build the C++ code and link the shared libraries.
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);
}
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”);
},
}
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.
#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.
#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:
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::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.
Import the generated bindings and other necessary libraries.
mod bindings;
use std::io::{self, Write};
use std::ffi::CString;
use bindings::*;
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);
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”);
}
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”);
}
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);
}
}
Run the program.
cargo clean
cargo run
Source Code
https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/9.x/rust