在项目上经常要用到身份证阅读器、护照阅读仪、指纹仪等各种品牌硬件,假如每套系统的都做集成开发那代码的维护成本将变得很高,为此采用rust来调用厂家提供的sdk c++开发包并封装成nodejs包,用fastify来开发成web api独立的服务形式。这样我们开发系统时只需调用web接口即可,跨平台又可共用,方便快捷,话不多说来看代码如何实现。

一、创建项目
安装rust后,打开vs新建一个工程目录,我们通过cargo new创建的一个package项目,加上--lib参数后创建的项目就是库项目(library package)。
cargo new --lib reader
package 就是一个项目,因此它包含有独立的 Cargo.toml 文件,用于项目配置。库项目只能作为三方库被其它项目引用,而不能独立运行,即src/lib.rs。
典型的package
如果一个 package 同时拥有 src/main.rs 和 src/lib.rs,那就意味着它包含两个包:库包和二进制包,这两个包名也都是 test_math —— 都与 package 同名。
一个真实项目中典型的 package,会包含多个二进制包,这些包文件被放在 src/bin 目录下,每一个文件都是独立的二进制包,同时也会包含一个库包,该包只能存在一个 src/lib.rs:
.
├── Cargo.toml
├── Cargo.lock
├── src
│ ├── main.rs
│ ├── lib.rs
│ └── bin
│ └── main1.rs
│ └── main2.rs
├── tests
│ └── some_integration_tests.rs
├── benches
│ └── simple_bench.rs
└── examples
└── simple_example.rs

唯一库包:src/lib.rs
默认二进制包:src/main.rs,编译后生成的可执行文件与package同名
其余二进制包:src/bin/main1.rs 和 src/bin/main2.rs,它们会分别生成一个文件同名的二进制可执行文件
集成测试文件:tests 目录下
性能测试benchmark文件:benches 目录下
项目示例:examples 目录下
这种目录结构基本上是 Rust 的标准目录结构,在 github 的大多数项目上,你都将看到它的身影。
运行Cargo build命令,我们在target\debug目录下可以看到编译后的结果。
二、Cargo.toml

[package]
name = "reader"
version = "0.1.0"
edition = "2018"
exclude = ["reader.node"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2.9"
libloading = "0.7"
once_cell = "1.8"
serde = { version = "1.0", features = ["derive"] }
widestring = "0.5.1"
serde_json = "1.0"
base64 = "0.13"
hex="0.4.2"
encoding = "0.2"
tokio={version="1.18.0",features = ["full"]}

[dependencies.neon]
version = "0.9"
default-features = false
features = ["napi-5", "channel-api"]

[lib]
crate-type = ["cdylib"]

三、package.json

{
  "name": "reader",
  "version": "0.1.0",
  "description": "",
  "main": "index.node",
  "scripts": {
    "build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
    "build-debug": "npm run build --",
    "build-release": "npm run build -- --release",
    "build_win32": "npm run build -- --release --target=i686-pc-windows-msvc",
    "test": "cargo test",
    "run": "cargo run"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cargo-cp-artifact": "^0.1"
  },
  "dependencies": {
    "express": "^4.17.3"
  }
}

我们可以打印rust看看编译输出支持哪些架构
rustc --print target-list
//添加 x86编译链接器
rustup target add i686-pc-windows-msvc

四、代码分析

use std::collections::HashMap;
use std::str;
use std::fmt::Write;
use std::io::{Error};

extern crate encoding;
use encoding::all::GB18030;
use encoding::{DecoderTrap,EncoderTrap,Encoding};

use tokio::time::{sleep, Duration,Instant};
use libc::{c_int, c_void};
use libloading::{Library, Symbol};
use neon::prelude::*;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};

use widestring::{WideCStr, WideCString, WideChar};
// 编码转换 utf8 -> utf16le
fn encode(source: &str) -> WideCString {
    let string_source = source.to_string() + "\0";
    WideCString::from_str(&string_source).unwrap()
}
// 解码转换 utf16le -> utf8
fn decode(source: &[WideChar]) -> String {
    WideCStr::from_slice_truncate(source)
        .unwrap()
        .to_string()
        .unwrap()
}
// 加载 dll
static LIBRARY: OnceCell<Library> = OnceCell::new();

//指定编译架构
static MACHINE_KIND: &str = if cfg!(target_os = "windows") {
    if cfg!(target_arch = "x86") {
        "win32"
    } else if cfg!(target_arch = "x86_x64") {
        "win64"
    } else {
        "other"
    }
} else if cfg!(target_os = "linux") {
    if cfg!(target_arch = "x86") {
        "linux32"
    } else if cfg!(target_arch = "x86_64") {
        "linux64"
    } else if cfg!(target_arch = "aarch64") {
        "aarch64"
    } else if cfg!(target_arch = "arm") {
        "arm"
    } else {
        "other"
    }
} else {
    "other"
};

//定义函数方法名,这里要根据c++库的函数名和参数来定义,函数名和参数类型务必要一致。
type LPCTSTR = *const WideChar;
type BOOL = c_int;
type INITPTR = *const i8;
type CANRST = *mut WideChar;

// 打开设备
type S2V7_open = unsafe extern "system" fn() -> c_int;
// 关闭设备
type S2V7_close = unsafe extern "system" fn() -> c_int;

 //【set mode  设置读证功能】
type S2V7_set_mode =
    unsafe extern "system" fn(flg_takeColor: c_int, flg_takeUV: c_int, flg_readChipInfo: c_int, flg_readChipFace: c_int) -> c_int; // Type = 0 即可

//【wait Doc. in 等待放卡】
type S2V7_wait_DocIn =
unsafe extern "system" fn(timeout: f64, flg_in: INITPTR) -> c_int; // Type = 0 即可


//【wait Doc. out 等待拿卡】
type S2V7_wait_DocOut =
unsafe extern "system" fn(timeout: f64, flg_out: INITPTR) -> c_int; // Type = 0 即可

 //【process  执行读卡过程】
type S2V7_process = unsafe extern "system" fn() -> c_int;

 //读取卡类型
type S2V7_get_cardType = unsafe extern "system" fn() -> c_int;

//保存彩照
type S2V7_VIS_saveColor = unsafe extern "system" fn(imgPath: LPCTSTR) -> c_int;
//保存红外照
type S2V7_VIS_saveIR = unsafe extern "system" fn(imgPath: LPCTSTR) -> c_int;

//【get MRZ text 获取OCR文字信息】
type S2V7_VIS_getMRZtext = unsafe extern "system" fn(text: LPCTSTR) -> c_int;

//show text information  文字信息
type S2V7_RDO_getBytesByIndex = unsafe extern "system" fn(index: c_int,data: LPCTSTR) -> c_int;
type S2V7_VIS_getBytesByIndex = unsafe extern "system" fn(index: c_int,data: LPCTSTR) -> c_int;

type S2V7_RF_active = unsafe extern "system" fn(antenna: c_int,atr: LPCTSTR, atr_len: c_int) -> c_int;

//构建函数实例
static V7_OPEN: OnceCell<Symbol<S2V7_open>> = OnceCell::new();
static V7_CLOSE: OnceCell<Symbol<S2V7_close>> = OnceCell::new();
static V7_SET_MODE: OnceCell<Symbol<S2V7_set_mode>> = OnceCell::new();
static V7_WAIT_DOCINT: OnceCell<Symbol<S2V7_wait_DocIn>> = OnceCell::new();
static V7_WAIT_DOCOUT: OnceCell<Symbol<S2V7_wait_DocOut>> = OnceCell::new();
static V7_PROCESS: OnceCell<Symbol<S2V7_process>> = OnceCell::new();
static V7_GET_CARDTYPE: OnceCell<Symbol<S2V7_get_cardType>> = OnceCell::new();
static V7_VIS_SAVECOLOR: OnceCell<Symbol<S2V7_VIS_saveColor>> = OnceCell::new();
static V7_VIS_SAVEIR: OnceCell<Symbol<S2V7_VIS_saveIR>> = OnceCell::new();
static V7_VIS_GETMRZTEXT: OnceCell<Symbol<S2V7_VIS_getMRZtext>> = OnceCell::new();
static V7_RDO_getBytesByIndex: OnceCell<Symbol<S2V7_RDO_getBytesByIndex>> = OnceCell::new();
static V7_VIS_getBytesByIndex: OnceCell<Symbol<S2V7_VIS_getBytesByIndex>> = OnceCell::new();
static V7_RF_active: OnceCell<Symbol<S2V7_RF_active>> = OnceCell::new();

// 对外导出函数方法
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("init", init_by_node)?;
    cx.export_function("start", start)?;
}

//加载dll并对函数进行初始化操作
pub fn init_by_node(mut cx: FunctionContext) -> JsResult<JsNumber> {
	//外部传进来的参数(根据自己的需要来定义)
    let directory = cx.argument::<JsString>(0)?.value(&mut cx);
    let userid = cx.argument::<JsString>(1)?.value(&mut cx);
    unsafe {
        DIRECTORY_PATH.take();
        DIRECTORY_PATH.set(directory).unwrap();
        USER_ID.take();
        USER_ID.set(userid).unwrap();
    };
    let result = init() as f64;
    Ok(cx.number(result))
}

//核心代码,加载dll函数并映射
fn init() -> c_int {
    let directory = unsafe { DIRECTORY_PATH.get().unwrap() };
    let userid = unsafe { USER_ID.get().unwrap() };
    let directory_path = std::path::Path::new(directory).join(MACHINE_KIND);
    if directory_path.exists() {
        let dll_path = directory_path.join(libloading::library_filename("STAR200_V7_DRV"));
        println!("dll_path: {:?}", dll_path);
        if dll_path.exists() {
            match init_dll(dll_path.to_str().unwrap()).is_ok() {
                true => {
                    // 打开设备
                    let init_result = unsafe {V7_OPEN.get_unchecked()()};
                    if init_result == 0 {
                        println!("设备打开成功");
                        return ResultType::Success as c_int;
                    } else {
                        println!("设备打开失败,代码:{:?}",init_result);
                        return ResultType::DeviceNotFound as c_int;
                    }
                }
                false => {
                    return ResultType::INITDLLFail as c_int;
                }
            }
        } else {
            return ResultType::DllPathNotExist as c_int;
        }
    } else {
        println!("{:?}", directory_path);
        return ResultType::DirectoryPathNotExist as c_int;
    }
}

// 加载dll
fn init_dll(dll_path: &str) -> Result<bool, Box<dyn std::error::Error>> {
    unsafe {
        if INITDLL {
            return Ok(true);
        }
    }
    println!("加载dll");
    println!("dll_path");
    let library = LIBRARY.get_or_init(|| unsafe { Library::new(dll_path).unwrap() });
    println!("S2V7_open");
    V7_OPEN.get_or_init(|| unsafe { library.get::<S2V7_open>(b"S2V7_open").unwrap() });
    println!("S2V7_close");
    V7_CLOSE.get_or_init(|| unsafe { library.get::<S2V7_close>(b"S2V7_close").unwrap() });
    println!("S2V7_set_mode");
    V7_SET_MODE.get_or_init(|| unsafe {library.get::<S2V7_set_mode>(b"S2V7_set_mode").unwrap()});
    println!("S2V7_wait_DocIn");
    V7_WAIT_DOCINT.get_or_init(|| unsafe { library.get::<S2V7_wait_DocIn>(b"S2V7_wait_DocIn").unwrap() });
    println!("S2V7_wait_DocOut");
    V7_WAIT_DOCOUT.get_or_init(|| unsafe { library.get::<S2V7_wait_DocOut>(b"S2V7_wait_DocOut").unwrap() });
    V7_PROCESS.get_or_init(|| unsafe { library.get::<S2V7_process>(b"S2V7_process").unwrap() });
    V7_GET_CARDTYPE.get_or_init(|| unsafe { library.get::<S2V7_get_cardType>(b"S2V7_get_cardType").unwrap() });
    V7_VIS_SAVECOLOR.get_or_init(|| unsafe { library.get::<S2V7_VIS_saveColor>(b"S2V7_VIS_saveColor").unwrap() });
    V7_VIS_SAVEIR.get_or_init(|| unsafe { library.get::<S2V7_VIS_saveIR>(b"S2V7_VIS_saveIR").unwrap() });
    V7_VIS_GETMRZTEXT.get_or_init(|| unsafe { library.get::<S2V7_VIS_getMRZtext>(b"S2V7_VIS_getMRZtext").unwrap() });
    V7_RDO_getBytesByIndex.get_or_init(|| unsafe { library.get::<S2V7_RDO_getBytesByIndex>(b"S2V7_RDO_getBytesByIndex").unwrap() });
    V7_VIS_getBytesByIndex.get_or_init(|| unsafe { library.get::<S2V7_VIS_getBytesByIndex>(b"S2V7_VIS_getBytesByIndex").unwrap() });
    V7_RF_active.get_or_init(|| unsafe { library.get::<S2V7_RF_active>(b"S2V7_RF_active").unwrap() });

    unsafe {
        INITDLL = true;
    }
    Ok(true)
}
//创建新线程来监测设备读证操作
fn start(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
    let mut channel = cx.channel();
    channel.reference(&mut cx);
    println!("start {}", channel.has_ref());
    let index = unsafe {
        DEVICE_START_INDEX += 1;
        DEVICE_START_INDEX
    };
    std::thread::spawn(move || {
        // Do the heavy lifting inside the background thread.
        device_start(callback, channel, index);
    });
    Ok(cx.undefined())
}

use std::sync::{Arc, Mutex};
fn device_start(callback: Root<JsFunction>, channel: Channel, index: u64) {
    let index = index;
    let callback = Arc::new(Mutex::new(callback));

    //设置读证功能
    unsafe { V7_SET_MODE.get_unchecked()(1,1,1,1) };

    loop {
        if index != unsafe { DEVICE_START_INDEX } {
            break;
        };
        let callback_clone = Arc::clone(&callback);
        let mut result = RecogIDCardEXResult::default();
        let mut flg_in:i8=0;
        match unsafe { V7_WAIT_DOCINT.get_unchecked()(5.0,&mut flg_in) } {
            // 设备正常 检测是否有放入证件
            0 => {
                if flg_in==0{
                    //检查是否放入超时
                    result.record_type = ResultType::CheckCardNotInOrOut as i32;
                    break;
                }
                result.device_online = true;
                result.device_name =unsafe { DEVCIE_NAME.get_or_init(|| "".to_string()).to_string() };

                match unsafe { V7_PROCESS.get_unchecked()() } {
                    // 证件有放入
                    0 => {
                        result.record_type = ResultType::CheckCardInput as i32;
                    }
                    // 未检测到OCR区域
                    -1 => {
                        result.record_type = ResultType::OCRFail as i32;
                    }
                    // 设备离线
                    -3 => {
                        result.device_online = false;
                        result.record_type = init();
                    }
                    _ => {
                        result.record_type = ResultType::Unknown as i32;
                    }
                }

            }
            -3 => {
                //设备离线
                let init = init();
                result.device_online = false;
                result.record_type = init;
            }
            _ => {
                //未知错误
                result.record_type = ResultType::Unknown as i32;
            }
        };

        if unsafe { *NEED_RECORD.get_or_init(|| false) } {
            println!("手工点击识别+1");
            result.record_type = ResultType::CheckCardInput as i32;
        }

        // let time_now = std::time::Instant::now();
        if result.record_type == ResultType::CheckCardInput as i32 {
            let _result = recog_card();
            result.success = _result.success;
            result.img_base64 = _result.img_base64;
            result.reg_info = _result.reg_info;
            result.card_type = _result.card_type;
            result.card_name = _result.card_name;
        }
        let neet_sendinfo = if Some(true) == unsafe { NEED_RECORD.take() } {
            true
        } else {
            false
        };

        // let elapsed = time_now.elapsed();
        // println!("识别时间结束时间 {:.6?}", elapsed);
        if result.record_type != ResultType::CheckCardNotInOrOut as i32
            && (*unsafe { RESULT_TYPE.get_or_init(|| -10000) } != result.record_type
                || result.record_type == ResultType::CheckCardInput as i32
                || neet_sendinfo)
        {
            unsafe {
                RESULT_TYPE.take();
                RESULT_TYPE.set(result.record_type).unwrap();
            }
            channel.send(move |mut cx| {
                let result_json = serde_json::to_string(&result).unwrap();
                let callback = callback_clone.lock().unwrap().to_inner(&mut cx);
                let this = cx.undefined();
                let args = vec![cx.string(&result_json)];
                callback.call(&mut cx, this, args)?;
                Ok(())
            });
        }
        std::thread::sleep(std::time::Duration::from_millis(20));
    }
}

完整源码

08-02 11:22