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. Thewalkdir
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 thelib
folder, create two files:bridge.cpp
andbridge.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
, theprintln!()
function won’t output anything to the console unless you addcargo: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.
#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 ofBarcode
structures. - The
create_barcode
function creates aBarcode
structure. - The
decode_barcode_file
function decodes barcodes from an image file. - The
free_barcode
function releases the memory allocated for theBarcodeResults
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;
}
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
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); }
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
Discover more from Coursity
Subscribe to get the latest posts sent to your email.