• 使用rust调用c++静态库并编译nodejs包


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    在项目上经常要用到身份证阅读器、护照阅读仪、指纹仪等各种品牌硬件,假如每套系统的都做集成开发那代码的维护成本将变得很高,为此采用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"]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    三、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"
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    我们可以打印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 = 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"
    };
    
    折叠 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    //定义函数方法名,这里要根据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> = OnceCell::new();
    static V7\_CLOSE: OnceCell> = OnceCell::new();
    static V7\_SET\_MODE: OnceCell> = OnceCell::new();
    static V7\_WAIT\_DOCINT: OnceCell> = OnceCell::new();
    static V7\_WAIT\_DOCOUT: OnceCell> = OnceCell::new();
    static V7\_PROCESS: OnceCell> = OnceCell::new();
    static V7\_GET\_CARDTYPE: OnceCell> = OnceCell::new();
    static V7\_VIS\_SAVECOLOR: OnceCell> = OnceCell::new();
    static V7\_VIS\_SAVEIR: OnceCell> = OnceCell::new();
    static V7\_VIS\_GETMRZTEXT: OnceCell> = OnceCell::new();
    static V7\_RDO\_getBytesByIndex: OnceCell> = OnceCell::new();
    static V7\_VIS\_getBytesByIndex: OnceCell> = OnceCell::new();
    static V7\_RF\_active: OnceCell> = OnceCell::new();
    
    折叠 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    // 对外导出函数方法
    #[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 {
     //外部传进来的参数(根据自己的需要来定义)
     let directory = cx.argument::(0)?.value(&mut cx);
     let userid = cx.argument::(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> {
     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::(b"S2V7\_open").unwrap() });
     println!("S2V7\_close");
     V7\_CLOSE.get\_or\_init(|| unsafe { library.get::(b"S2V7\_close").unwrap() });
     println!("S2V7\_set\_mode");
     V7\_SET\_MODE.get\_or\_init(|| unsafe {library.get::(b"S2V7\_set\_mode").unwrap()});
     println!("S2V7\_wait\_DocIn");
     V7\_WAIT\_DOCINT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_wait\_DocIn").unwrap() });
     println!("S2V7\_wait\_DocOut");
     V7\_WAIT\_DOCOUT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_wait\_DocOut").unwrap() });
     V7\_PROCESS.get\_or\_init(|| unsafe { library.get::(b"S2V7\_process").unwrap() });
     V7\_GET\_CARDTYPE.get\_or\_init(|| unsafe { library.get::(b"S2V7\_get\_cardType").unwrap() });
     V7\_VIS\_SAVECOLOR.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_saveColor").unwrap() });
     V7\_VIS\_SAVEIR.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_saveIR").unwrap() });
     V7\_VIS\_GETMRZTEXT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_getMRZtext").unwrap() });
     V7\_RDO\_getBytesByIndex.get\_or\_init(|| unsafe { library.get::(b"S2V7\_RDO\_getBytesByIndex").unwrap() });
     V7\_VIS\_getBytesByIndex.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_getBytesByIndex").unwrap() });
     V7\_RF\_active.get\_or\_init(|| unsafe { library.get::(b"S2V7\_RF\_active").unwrap() });
    
     unsafe {
     INITDLL = true;
     }
     Ok(true)
    }
    折叠 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    //创建新线程来监测设备读证操作
    fn start(mut cx: FunctionContext) -> JsResult {
     let callback = cx.argument::(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, 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));
     }
    }
    折叠 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119

    完整源码

    点击查看代码

    use std::collections::HashMap;
    
    use libc::{c_int, c_void};
    use libloading::{Library, Symbol};
    use neon::prelude::*;
    use once_cell::sync::OnceCell;
    use serde::Serialize;
    
    extern crate encoding;
    use encoding::all::GB18030;
    use encoding::{DecoderTrap,EncoderTrap,Encoding};
    
    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 = 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"
    };
    
    //设置识别的证件 ID
    // 设置当前要识别的证件类型,并将
    // 之前已经设置的证件类型清除。
    // nMainID 主要识别类型,nSubID 子类型
    // nSubID 头指针,默认将数组
    // nSubID 第 一 个 元 素 赋 值 为 0 即
    // nSubID[0]=0
    // type S = c\_int[];
    
    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> = OnceCell::new();
    static V7\_CLOSE: OnceCell> = OnceCell::new();
    static V7\_SET\_MODE: OnceCell> = OnceCell::new();
    static V7\_WAIT\_DOCINT: OnceCell> = OnceCell::new();
    static V7\_WAIT\_DOCOUT: OnceCell> = OnceCell::new();
    static V7\_PROCESS: OnceCell> = OnceCell::new();
    static V7\_GET\_CARDTYPE: OnceCell> = OnceCell::new();
    static V7\_VIS\_SAVECOLOR: OnceCell> = OnceCell::new();
    static V7\_VIS\_SAVEIR: OnceCell> = OnceCell::new();
    static V7\_VIS\_GETMRZTEXT: OnceCell> = OnceCell::new();
    static V7\_RDO\_getBytesByIndex: OnceCell> = OnceCell::new();
    static V7\_VIS\_getBytesByIndex: OnceCell> = OnceCell::new();
    static V7\_RF\_active: OnceCell> = OnceCell::new();
    
    // static
    static mut INITDLL: bool = false;
    static mut DEVICE\_START\_INDEX: u64 = 0;
    static mut DIRECTORY\_PATH: OnceCell = OnceCell::new();
    static mut USER\_ID: OnceCell = OnceCell::new();
    static mut DEVCIE\_NAME: OnceCell = OnceCell::new();
    static mut RESULT\_TYPE: OnceCell = OnceCell::new();
    static mut NEED\_RECORD: OnceCell = OnceCell::new();
    // 初始化dll
    fn init\_dll(dll\_path: &str) -> Result> {
     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::(b"S2V7\_open").unwrap() });
     println!("S2V7\_close");
     V7\_CLOSE.get\_or\_init(|| unsafe { library.get::(b"S2V7\_close").unwrap() });
     println!("S2V7\_set\_mode");
     V7\_SET\_MODE.get\_or\_init(|| unsafe {library.get::(b"S2V7\_set\_mode").unwrap()});
     println!("S2V7\_wait\_DocIn");
     V7\_WAIT\_DOCINT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_wait\_DocIn").unwrap() });
     println!("S2V7\_wait\_DocOut");
     V7\_WAIT\_DOCOUT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_wait\_DocOut").unwrap() });
     V7\_PROCESS.get\_or\_init(|| unsafe { library.get::(b"S2V7\_process").unwrap() });
     V7\_GET\_CARDTYPE.get\_or\_init(|| unsafe { library.get::(b"S2V7\_get\_cardType").unwrap() });
     V7\_VIS\_SAVECOLOR.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_saveColor").unwrap() });
     V7\_VIS\_SAVEIR.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_saveIR").unwrap() });
     V7\_VIS\_GETMRZTEXT.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_getMRZtext").unwrap() });
     V7\_RDO\_getBytesByIndex.get\_or\_init(|| unsafe { library.get::(b"S2V7\_RDO\_getBytesByIndex").unwrap() });
     V7\_VIS\_getBytesByIndex.get\_or\_init(|| unsafe { library.get::(b"S2V7\_VIS\_getBytesByIndex").unwrap() });
     V7\_RF\_active.get\_or\_init(|| unsafe { library.get::(b"S2V7\_RF\_active").unwrap() });
    
     unsafe {
     INITDLL = true;
     }
     Ok(true)
    }
    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;
     }
    }
    pub fn init\_by\_node(mut cx: FunctionContext) -> JsResult {
     let directory = cx.argument::(0)?.value(&mut cx);
     let userid = cx.argument::(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))
    }
    #[allow(dead\_code)] // 允许dead\_code
    enum ResultType {
     DirectoryPathNotExist = -2003, // 找不到运行目录
     DllPathNotExist = -2001, // 找不到dll文件
     INITDLLFail = -2000, // 初始化dll
     Success = 0, // 成功
     UserIdFail = 2001, //用户 ID 错误
     DeviceInitFail = 2002, // 设备初始化失败
     DeviceKernelInitFail = 2003, // 初始化核心失败
     DeviceDatInitFail = 2004, //未找到授权文件
     DeviceNotInit = 2101, // 设备未初始化
     DeviceNotFound = 2102, // 没有找到设备
     DeviceReConnect = 2103, // 重新连接设备
     Unknown = -100, // 未知错误
     CheckCardInput = 3001, // 证件放入设备
     CheckCardOut = 3002, // 证件移出设备
     CheckCardNotInOrOut = 3000, // 证件无放入或拿出
     CheckCardBarCode = 3003, // 检测到手机条码
     OCRFail=-1, // 未检测到OCR区域
    }
    
    type RecogIDCardEXResultItem = HashMap;
    #[derive(Default, Serialize)]
    pub struct RecogIDCardEXResultObject {
     pub viz\_result: RecogIDCardEXResultItem,
     pub viz\_orc\_result: RecogIDCardEXResultItem,
     pub mrz\_result: RecogIDCardEXResultItem,
     pub mrz\_ocr\_result: RecogIDCardEXResultItem,
     pub chip\_result: RecogIDCardEXResultItem,
    }
    
    #[derive(Default, Serialize)]
    pub struct RecogIDCardEXResult {
     pub device\_name: String,
     pub device\_online: bool,
     pub reg\_info: RecogIDCardEXResultObject,
     pub img\_base64: HashMap,
     pub card\_type: i32,
     pub record\_type: i32,
     pub card\_name: String,
     pub success: bool, // 识别是否成功
    }
    
    static SAVE\_IMAGE\_REUSLT\_NAME: [&str; 5] = [
     "tempHeadEC.jpg",
     "tempHead.jpg",
     "tempUV.jpg",
     "tempIR.jpg",
     "temp.jpg",
    ];
    
    fn start(mut cx: FunctionContext) -> JsResult {
     let callback = cx.argument::(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, 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;
     }
     // 未找到非接卡
     // v if v/10 == -21 => {
     // 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));
     }
    }
    
    
    // 白光图、红外
    // 图、紫外图、版面头像和芯片头像
    pub fn recog\_card() -> RecogIDCardEXResult {
     let time\_now = std::time::Instant::now();
     let mut result = RecogIDCardEXResult::default();
     result.device\_online = true;
    
     let img\_path\_directory = std::path::Path::new(unsafe { DIRECTORY\_PATH.get().unwrap() });
    
     let ir\_img\_path = img\_path\_directory.join("ir.jpg");
     let color\_img\_path = img\_path\_directory.join("color.jpg");
    
     //显示红外照
     let irResult = unsafe {V7\_VIS\_SAVEIR.get\_unchecked()(encode(ir\_img\_path.to\_str().unwrap()).as\_ptr() as LPCTSTR)};
     //显示彩照
     let colorResult = unsafe {V7\_VIS\_SAVECOLOR.get\_unchecked()(encode(color\_img\_path.to\_str().unwrap()).as\_ptr() as LPCTSTR)};
    
     if irResult==0{
     if ir\_img\_path.exists() {
     match std::fs::read(&ir\_img\_path) {
     Ok(image\_data) => {
     println!("读取照片成功");
     let image\_data = base64::encode(&image\_data);
     let base64\_string = String::from("data:image/jpg;base64,");
     std::fs::remove\_file(ir\_img\_path).unwrap();
     result.img\_base64.insert("0".to\_string(), base64\_string + &image\_data);
     }
     Err(e) => {
     println!("读取照片抛异常");
     println!(
     "{:?} {:?}",
     e,
     "ir.jpg",
     );
     }
     };
     }
     }
    
     if colorResult==0{
     if color\_img\_path.exists() {
     match std::fs::read(&color\_img\_path) {
     Ok(image\_data) => {
     println!("读取照片成功");
     let image\_data = base64::encode(&image\_data);
     let base64\_string = String::from("data:image/jpg;base64,");
     std::fs::remove\_file(color\_img\_path).unwrap();
     result.img\_base64.insert("1".to\_string(), base64\_string + &image\_data);
     }
     Err(e) => {
     println!("读取照片抛异常");
     println!(
     "{:?} {:?}",
     e,
     "color.jpg",
     );
     }
     };
     }
     }
    
     let mut ocritem = RecogIDCardEXResultObject::default();
    
     let mut index: c\_int = 0;
    
     //orc识别文字
     let mut mrztext = [0; 1024];
     let x = unsafe {V7\_VIS\_GETMRZTEXT.get\_unchecked()(mrztext.as\_mut\_ptr())};
     if x==0{
     let result\_item = ["MRZ".to\_string(), decode(&mrztext)];
     ocritem.mrz\_result.insert(index, result\_item);
     index+=1;
     }
    
     let mut data:[u16; 256] = [0; 256];
     let mut len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(0,data.as\_mut\_ptr())};
     if len>0{
     ocritem.mrz\_result.insert(index, ["编号".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(1,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["国籍".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(2,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["民族".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(3,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["姓名".to\_string(), decode(&data)]);
     index+=1;
    
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(4,data.as\_mut\_ptr())};
     let cardType= unsafe {V7\_GET\_CARDTYPE.get\_unchecked()()};
     if cardType==101{
     //身份证是UTF8格式
     ocritem.mrz\_result.insert(index, ["中文名".to\_string(), decode(&data)]);
     }else{
     ocritem.mrz\_result.insert(index, ["中文名".to\_string(), decode(&data)]);
     // //中国护照的中文姓名 是GBK编码的
     // let name=GB18030.decode(&data, DecoderTrap::Strict).unwrap();
     // ocritem.mrz\_result.insert(index, ["中文名".to\_string(), name.replace("\u{0}","")]);
     }
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(5,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["性别".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(6,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["出生日期".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(7,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["地址".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(8,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["签发机关".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(9,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["有效期始".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(10,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["有效期止".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_RDO\_getBytesByIndex.get\_unchecked()(20,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["港澳台ID".to\_string(), decode(&data)]);
     index+=1;
     }
     else{
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(0,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["编号".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(1,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["国籍".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(2,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["民族".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(3,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["姓名".to\_string(), decode(&data)]);
     index+=1;
    
     
    
     //中国护照的中文姓名 是GBK编码的, 身份证不会执行到这里
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(4,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["中文名".to\_string(), decode(&data)]);
     // let name=GB18030.decode(&data, DecoderTrap::Strict).unwrap();
     // ocritem.mrz\_result.insert(index, ["中文名".to\_string(), name.replace("\u{0}","")]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(5,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["性别".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(6,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["出生日期".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(7,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["地址".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(8,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["签发机关".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(9,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["有效期始".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(10,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["有效期止".to\_string(), decode(&data)]);
     index+=1;
    
     len = unsafe {V7\_VIS\_getBytesByIndex.get\_unchecked()(20,data.as\_mut\_ptr())};
     ocritem.mrz\_result.insert(index, ["港澳台ID".to\_string(), decode(&data)]);
     index+=1;
     }
     
    
     result.reg\_info=ocritem;
     result.success = true;
    
     result.card\_type = unsafe {V7\_GET\_CARDTYPE.get\_unchecked()()};
     let elapsed = time\_now.elapsed();
     println!("{:.6?}", elapsed);
     return result;
    }
    
    pub fn regcord\_by\_node(mut cx: FunctionContext) -> JsResult {
     println!("regcord\_by\_node");
     unsafe {
     NEED\_RECORD.take();
     NEED\_RECORD.set(true).unwrap();
     };
     Ok(cx.null())
    }
    #[neon::main]
    fn main(mut cx: ModuleContext) -> NeonResult<()> {
     cx.export\_function("init", init\_by\_node)?;
     cx.export\_function("start", start)?;
     cx.export\_function("regcord\_by\_node", regcord\_by\_node)?;
     Ok(())
    }
    
    折叠 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
  • 相关阅读:
    KNN——水果分类
    【代码精读】optee的异常向量表
    日历视图,轻松解决时间管理难题_三叠云
    使用Compose实现基于MVI架构、retrofit2、支持 glance 小部件的TODO应用
    CSP-J/S信息学奥赛-数据结构
    嵌入式开发--STM32硬件SPI驱动74HC595
    基于Hardhat和Openzeppelin开发可升级合约(一)
    SAP Table function 执行报错 code CX_SQL_EXCEPTION feature not supported 该如何分析
    【CC3200AI 实验教程 1】疯壳·AI语音人脸识别(会议记录仪/人脸打卡机)-开发环境搭建
    雷达阵列天线孔径的概念及其相关意义
  • 原文地址:https://blog.csdn.net/m0_56069948/article/details/126120044