全部文章

<p>最近想做一个应用用来替代 C-Lodop,功能比 C-Lodop 更少一些,因为只针对热敏打印机。简单点就是提供一个 WebSocket 服务,这个服务提供:</p> <ul> <li>获取打印机列表</li> <li>预览</li> <li>编辑</li> <li>打印</li> </ul> <p>要求:</p> <ul> <li>支持 Windows 系统(32位、64位)Windows 7 以上版本。</li> <li>支持 MacOS 系统</li> <li>支持主流 Linux 系统。如 Ubuntu。</li> <li>支持国产操作系统。如:统信、麒麟。</li> <li>应用关闭时不是退出,而是最小化到托盘。</li> <li>能自定义 WebSocket 服务监听的端口。</li> <li>能启动 WebSocket 服务。</li> <li>能关闭 WebScoket 服务。</li> </ul> <p><img src="/uploads/web-print-arch.png" alt="" /></p>
详情
<p>前段时间工作中遇到一个需求,node.js 需要调用 dll 提供的一些方法来实现业务需求。</p> <p>一开始,调研了现有的一些 node.js 库,如:<code>ffi-napi</code>、<code>koffi</code>,最终发现调用方写的代码太痛苦了。 如使用 koffi,举个例子:</p> <pre><code class="language-js">const koffi = require("koffi"); // Load the shared library const lib = koffi.load("user32.dll"); // Declare constants const MB_OK = 0x0; const MB_YESNO = 0x4; const MB_ICONQUESTION = 0x20; const MB_ICONINFORMATION = 0x40; const IDOK = 1; const IDYES = 6; const IDNO = 7; // Find functions const MessageBoxA = lib.func("__stdcall", "MessageBoxA", "int", [ "void *", "str", "str", "uint", ]); const MessageBoxW = lib.func("__stdcall", "MessageBoxW", "int", [ "void *", "str16", "str16", "uint", ]); let ret = MessageBoxA( null, "Do you want another message box?", "Koffi", MB_YESNO | MB_ICONQUESTION ); if (ret == IDYES) { MessageBoxW(null, "Hello World!", "Koffi", MB_ICONINFORMATION); } </code></pre> <p>对于前端开发,压根不懂 <code>__stdcall, void *, str16, unit</code> 这些。</p> <p>理想的(期望)调用方式如下:</p> <pre><code class="language-js">const addon = require("addon"); let ret = addon.MessageBoxA(null, "Do you want another message box?", "Koffi", MB_YESNO | MB_ICONQUESTION );</code></pre> <p>为了实现理想的调用方式,那就不能用通用的解决方案(<code>ffi-napi</code>、<code>koffi</code>),需要基于<a href="https://nodejs.org/docs/latest-v16.x/api/n-api.html">C/C++ addons with Node-API</a>,但这个方案也有缺点,就是你需要会 C/C++,如果你不会或者你团队没有会 C/C++ 的,那我建议你还是 <code>koffi</code> 这种通用的解决方案。</p> <p>那就上一张图吧,具体的代码就不放上来,在这里只希望给遇到同样问题的人提供一个思路,具体的代码需要自己去探索。</p> <p><img src="/uploads/%E6%97%A0%E6%A0%87%E9%A2%98-2024-02-05-0935.png" alt="" /></p>
详情
<p>最近在做一个 Electron 项目,遇到了 Error: A dynamic link library (DLL) initialization routine failed.</p> <p><img src="/uploads/2024-06-07_14-53-27.jpg" alt="" /></p> <p>折腾了两天,趁今天有时间,记录一下。 先说解决方案:</p> <pre><code>// binding.gyp // 删除 "msbuild_settings": { "ClCompile": { "ExceptionHandling": "Async" } }</code></pre> <p>网上找的资料,大多说的是 node.js 的版本和 electron 自带的版本不匹配,而我遇到的这种情况很特殊。</p> <h1>背景</h1> <p>我的 C++ addon 是基于 node-addon-api 写的,依赖了 setupapi.lib。</p> <p>本地环境:</p> <ul> <li>node: v16.14.2</li> <li>node-gyp: v10.1.0</li> <li>Pyhton: 3.11.3</li> <li>VS2022: 17.8.34330.188</li> </ul> <p>electron 版本信息:</p> <pre><code>{ node: '16.16.0', v8: '10.6.194.27-electron.0', uv: '1.43.0', zlib: '1.2.12.1-motley', brotli: '1.0.9', ares: '1.18.1', modules: '109', nghttp2: '1.47.0', napi: '8', llhttp: '6.0.7', openssl: '1.1.1', cldr: '41.0', icu: '71.1', tz: '2022e', unicode: '14.0', electron: '21.4.4', chrome: '106.0.5249.199' }</code></pre> <h1>现象</h1> <ol> <li>本机 node 执行 <code>node app.js</code>,正常。</li> <li>Electron 执行 <code>npm run start</code> -> <code>electron main.js --enable-logging</code>,则报错。</li> </ol>
详情
<p>用 C++ 写一个 nodejs addon。</p> <p>我的系统是 MacOS 10.15.7。</p> <h1>一、安装 node-gyp</h1> <p>我是全局安装。</p> <pre><code>npm i node-gyp -g</code></pre> <p>注意:node-gyp 依赖 <code>Python</code> 和 <code>Xcode Command Line Tools</code>,详细信息请参考:<a href="https://github.com/nodejs/node-gyp">https://github.com/nodejs/node-gyp</a></p> <h1>二、写 bindding.gyp</h1> <pre><code>{ "targets": [ { "target_name": "addon", "sources": [ "hello.cc" ] } ] }</code></pre> <h1>三、编写 hello.cc</h1> <pre><code class="language-c++">#include <node.h> namespace demo { using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; using v8::Object; using v8::String; using v8::Value; void Method(const FunctionCallbackInfo<Value> &args) { Isolate *isolate = args.GetIsolate(); // 返回一个字符串 world args.GetReturnValue().Set(String::NewFromUtf8( isolate, "world") .ToLocalChecked()); } // 初始化,接收一个 exports 对象 void Initialize(Local<Object> exports) { // 在 exports 上导出一个方法,方法名是 hello,方法的实现是 Method NODE_SET_METHOD(exports, "hello", Method); } // NODE_MODULE 是宏。用来初始化一个 nodejs 模块 NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) }</code></pre> <h1>四、配置和编译</h1> <h2>配置</h2> <pre><code class="language-bash">node-gyp configure</code></pre> <h2>编译</h2> <p>// 默认编译输出的目录是 <code>build/Release</code></p> <pre><code class="language-bash">node-gyp build // 如果遇到错误,可以尝试 node-gyp rebuild</code></pre> <h1>五、nodejs 调用 .node</h1> <pre><code class="language-js">// demo.js const addon = require("./build/Release/addon"); console.log(addon.hello()); // 输出 world</code></pre> <h1>参考</h1> <p><a href="https://nodejs.org/docs/latest-v16.x/api/addons.html">https://nodejs.org/docs/latest-v16.x/api/addons.html</a></p> <p><a href="https://github.com/nodejs/node-gyp">https://github.com/nodejs/node-gyp</a></p>
详情
<p>最近在研究替换 C-Lodop 的方案。</p> <h1>方案一:C++ addon</h1> <p>C++ addon 是用 C++ 编写的动态链接的共享对象。require() 函数可以将插件加载为普通的Node.js 模块。插件提供了 JavaScript 和 C/C++ 库之间的接口。</p> <p>实现插件的三种方式:</p> <table> <thead> <tr> <th>实现插件的方式</th> <th>优点</th> <th>缺点</th> </tr> </thead> <tbody> <tr> <td>Node-API</td> <td>可移植、可跨不同 Node.js 版本</td> <td></td> </tr> <tr> <td>nan</td> <td>不用考虑 node.js 版本</td> <td></td> </tr> <tr> <td>直接使用内部 V8、libuv 和 Node.js 库</td> <td></td> <td>实现复杂</td> </tr> </tbody> </table> <p>Node-API(以前的 N-API)是一种用于构建本机插件的 API。</p> <h1>方案二:ffi-napi</h1> <p>ffi-napi 是一个 Node.js 模块,它提供了一个简单的方式来调用外部(本地)的 C/C++ 函数,而无需编写 C++ 插件。它使用了 Node.js 的 N-API 来实现跨版本的稳定性,并且具有良好的跨平台兼容性。</p> <p>优点:不用编写 C++ 代码或者构建 C++ 插件。</p> <p>缺点:安装 ffi-napi 需要 node-gyp 构建、Python、msvs。项目已不再更新,最后一次更新 2021-03-18</p> <p><a href="https://github.com/node-ffi-napi/node-ffi-napi">https://github.com/node-ffi-napi/node-ffi-napi</a></p> <h1>方案三:koffi</h1> <p>Koffi 是 Node.js 的一个快速且易用的 C FFI 模块。</p> <p>特性:</p> <p>1、低开销,高性能。</p> <p>2、通过引用(指针)和值支持基元和聚合数据类型(结构和固定大小的数组)。</p> <p>3、Javascript函数可以用作C回调(自1.2.0起)。</p> <p><a href="https://koffi.dev/">https://koffi.dev/</a></p> <p><a href="https://github.com/Koromix/rygel/">https://github.com/Koromix/rygel/</a></p> <h1>名词解释:</h1> <p>FFI: Foreign Function Interface</p> <p>NAN: Native Abstractions for Node.js</p> <h1>参考</h1> <ul> <li><a href="https://zhuanlan.zhihu.com/p/687513603">https://zhuanlan.zhihu.com/p/687513603</a></li> <li><a href="https://nodejs.org/docs/latest-v16.x/api/addons.html#node-api">https://nodejs.org/docs/latest-v16.x/api/addons.html#node-api</a></li> <li><a href="https://github.com/nodejs/node-gyp#installation">https://github.com/nodejs/node-gyp#installation</a></li> </ul>
详情
<h1>CMake 是什么?</h1> <p>一个强大的软件构建系统。典型用途是用来构建 C/C++ 编写的项目。</p> <h1>1安装</h1> <h2>1.1 安装 CMake</h2> <p>我安装的是 3.25.3 版本 <a href="https://github.com/Kitware/CMake/releases/download/v3.25.3/cmake-3.25.3-windows-x86_64.msi"> cmake-3.25.3-windows-x86_64.msi</a></p> <p>如果想下载其它版本,进入 <a href="https://cmake.org/download/">https://cmake.org/download/</a> 找到需要的版本。</p> <h2>1.2 安装 Visual Studio Code 插件</h2> <h3>1.2.1 CMake</h3> <p><a href="https://marketplace.visualstudio.com/items?itemName=twxs.cmake">Cmake</a> 高亮显示 CMakeLists.txt 语法。</p> <h3>1.2.2 CMake Tools</h3> <p><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools">CMake Tools</a> 用来配置和执行 cmake 命令。</p> <h1>2新建项目</h1> <h2>2.1 新建目录</h2> <p>cmake-demo</p> <h2>2.2 新建 CMakeLists.txt</h2> <pre><code class="language-txt">cmake_minimum_required(VERSION 3.10) project(demo) # set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_FLAGS "-m32") set(CMAKE_VERBOSE_MAKEFILE on) # include_directories(${OPENGL_INCLUDE_DIR} ${GLEW_INCLUDE_DIRS}) add_executable(demo demo.cc)</code></pre> <h2>2.3 新建 demo.cc</h2> <pre><code class="language-c++">#include <stdio.h> int main() { printf("Hello CMake\n"); return 0; }</code></pre> <h2>2.4 选则编译工具</h2> <p>我选择的是 <code>Visual Studio 生成工具 2019 Release - x86</code></p> <p><img src="/uploads/Snipaste_2024-09-11_10-13-37.png" alt="" /></p> <h2>2.5 运行</h2> <p>如果编译通过的话,会自动运行,控制台会输出 <code>Hello CMake</code></p> <p><img src="/uploads/Snipaste_2024-09-11_10-18-10.png" alt="" /></p> <h1>参考</h1> <p><a href="https://cmake.org/cmake/help/latest/guide/tutorial/index.html">https://cmake.org/cmake/help/latest/guide/tutorial/index.html</a></p> <p><a href="https://zhuanlan.zhihu.com/p/653282782">https://zhuanlan.zhihu.com/p/653282782</a></p>
详情
<p>由于 jsbarcode 不支持 Code93 条码,所以只能自己动手。</p> <p>之前有想过用 <code>bwip-js</code> 替换 <code>jsbarcode</code>,但由于需求要支持更改条码内文字的字体,<code>bwip-js</code> 虽然也能<a href="https://juejin.cn/post/7297024168604549156">修改字体</a>,但是还要加载字体文件,这个方案需要修改的文件太多,也怕引入其它 bug,最终没有采取这个方案。</p> <p>首先,需要了解一下 Code 93</p> <h1>Code 93</h1> <p>1982 年 Intermec 公司设计;主要被加拿大邮政使用。<a href="https://en.wikipedia.org/wiki/Code_93">参考</a></p> <h2>允许的字符</h2> <p><code>0-9</code>、<code>A-Z</code>、<code>-</code>、<code>.</code>、<code>$</code>、<code>/</code>、<code>+</code>、<code>%</code>、<code>空格</code></p> <h2>结构</h2> <p><img src="/uploads/code93-structure.jpg" alt="" /></p> <h1>实战</h1> <h2>1 克隆 github 仓库</h2> <pre><code>git clone https://github.com/lindell/JsBarcode.git</code></pre> <h2>2 添加 Code93 代码,参考<a href="https://github.com/elysiumphase/bitgener/blob/master/src/Barcode/Code93.js">Code 93</a></h2> <p><code>src/barcodes</code> 下新建 <code>CODE93</code> 目录,新建 <code>index.js</code> 文件</p> <pre><code class="language-js">// index.js // Encoding documentation: // https://en.wikipedia.org/wiki/Code_93#Encoding import Barcode from "../Barcode.js"; const encoding = [ "100010100", ... ]; class CODE93 extends Barcode { constructor(data, options) { data = data.toUpperCase(); super(data, options); } encode() { const data = this.data; const table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%____*"; // _ => ($), (%), (/) et (+) const dataToEncode = data.toUpperCase(); const dataToEncodeLength = dataToEncode.length; let digit = ""; // start : * digit += encoding[47]; // digits for (let i = 0; i < dataToEncodeLength; i += 1) { digit += encoding[table.indexOf(dataToEncode.charAt(i))]; } // checksum let weightC = 0; let weightSumC = 0; let weightK = 1; // start at 1 because the right-most character is 'C' checksum let weightSumK = 0; for (let i = dataToEncodeLength - 1; i >= 0; i -= 1) { weightC = weightC === 20 ? 1 : weightC + 1; weightK = weightK === 15 ? 1 : weightK + 1; const index = table.indexOf(dataToEncode.charAt(i)); weightSumC += weightC * index; weightSumK += weightK * index; } const c = weightSumC % 47; weightSumK += c; const k = weightSumK % 47; digit += encoding[c]; digit += encoding[k]; // stop : * digit += encoding[47]; // terminaison bar digit += "1"; const encodedData = { data: digit, text: dataToEncode, }; return encodedData; } valid() { return /^[0-9a-zA-Z-. $/+%]+$/.test(this.data); } } export { CODE93 }; </code></pre> <h2>3 编译打包</h2> <p>在 <code>jsbarcode</code> 仓库根目录执行 <code>npm run build</code></p> <h2>4 本地调试</h2> <ol> <li>在 <code>jsbarcode</code> 仓库根目录执行 <code>npm link</code></li> <li>在使用 <code>jsbarcode</code> 的仓库执行 <code>npm link jsbarcode</code></li> <li>使用 <pre><code class="language-javascript">JsBarcode("#barcode", "1234", { format: "CODE93", width: 4, height: 40, displayValue: false, });</code></pre></li> <li>显示结果</li> </ol> <p><img src="/uploads/code93.jpg" alt="" /></p> <h1>参考</h1> <p><a href="https://en.wikipedia.org/wiki/Code_93">维基百科 Code 93</a></p> <p><a href="https://github.com/elysiumphase/bitgener/blob/master/src/Barcode/Code93.js">Code 93</a></p>
详情
<p>继上一篇文章<a href="http://pcdeng.com/rust-libusb-gnu.html">《Windows 11 搭建 Rust USB 开发环境之 GNU 版》</a>,这篇主要总结一下安装 <code>msvc</code> 工具链的要点。</p> <h1>前置</h1> <ol> <li>Windows 11 系统</li> <li><a href="https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/">Microsoft C++ 生成工具</a>,我装的是 2022 版,还有一些依赖,如 <code>Windows 11 SDK</code>,参考 <a href="https://zhuanlan.zhihu.com/p/507532813">Windows上Rust所依赖的msvc到底怎么装?</a></li> <li>rust 及其相关工具,如 <code>cargo</code>、<code>rustup</code>、<code>rustc</code>。一般安装好 rust,这些工具都会包含在内。</li> <li>下载 <code>libusb</code> 编译好的库,我下载的是 <a href="https://github.com/libusb/libusb/releases/download/v1.0.26/libusb-1.0.26-binaries.7z">libusb-1.0.26</a></li> </ol> <h1>实战</h1> <p>项目根目录假定是 <code>D:\projects\rust\libusb-demo</code></p> <h2>创建一个 rust 项目</h2> <p>打开一个<code>命令提示符</code>后,执行以下命令</p> <pre><code class="language-cmd">cargo new libusb-demo cd libusb-demo cargo add libusb code .</code></pre> <h2>写调用 <code>libusb</code> 的测试代码</h2> <p>在 Visual Studio Code 编辑器中编辑 <code>src/main.rs</code></p> <pre><code class="language-rs">extern crate libusb; fn main() { let context = libusb::Context::new().unwrap(); for device in context.devices().unwrap().iter() { let device_desc = device.device_descriptor().unwrap(); println!("Bus {:03} Device {:03} ID {:04x}:{:04x}", device.bus_number(), device.address(), device_desc.vendor_id(), device_desc.product_id()); } }</code></pre> <h2>配置</h2> <ol> <li>解压 <code>libusb-1.0.26-binaries.7z</code> 得到 <code>libusb-1.0.26-binaries</code>。</li> <li>把 <code>libusb-1.0.26-binaries</code> 下的 <code>VS2015-x64</code> 复制到 <code>D:\projects\rust\libusb-demo</code> 下,并改名为 <code>libusb-x64</code></li> <li>在 <code>libusb-demo</code> 下新建 <code>.cargo/config.toml</code> <pre><code class="language-toml">// .cargo/config.toml [target.x86_64-pc-windows-msvc.'usb-1.0'] # rustc-link-search = ['D:\projects\rust\libusb-demo\libusb-x64\dll'] # 这种是动态链接方式 rustc-link-search = ['D:\projects\rust\libusb-demo\libusb-x64\lib'] # 这种是静态链接方式 rustc-link-lib = ['libusb-1.0']</code></pre></li> </ol> <h2>编译正式版</h2> <pre><code class="language-cmd">cargo build --release</code></pre> <h2>验证</h2> <h3>静态链接版</h3> <p>复制 <code>target\release\libusb-demo.exe</code> 到虚拟机里或另一台装有 Windows 的系统运行。 打开 <code>命令提示符</code>,进入 <code>C:\Users\Payton\Desktop</code></p> <pre><code class="language-cmd">C:\Users\Payton\Desktop>libusb-demo.exe Bus 003 Device 001 ID 0e0f:0003 Bus 001 Device 002 ID 0e0f:0002 Bus 003 Device 000 ID 15ad:0779 Bus 002 Device 000 ID 15ad:0770 Bus 003 Device 002 ID 0e0f:0002 Bus 003 Device 003 ID 0e0f:0002 Bus 001 Device 000 ID 15ad:0774</code></pre> <h3>动态链接版</h3> <p>复制 <code>target\release\libusb-demo.exe</code> 和 <code>libusb-x64\dll\libusb-1.0.dll</code> 到虚拟机里或另一台装有 Windows 的系统运行。 打开 <code>命令提示符</code>,进入 <code>C:\Users\Payton\Desktop</code>,</p> <ul> <li>如果没有复制 <code>libusb-1.0.dll</code></li> </ul> <pre><code class="language-cmd">C:\Users\Payton\Desktop>libusb-demo.exe</code></pre> <p>提示 <img src="//www.pcdeng.com/uploads/libusb-1.0.dll-not-found.jpg" alt="" /></p> <ul> <li>复制 <code>libusb-1.0.dll</code> <pre><code class="language-cmd">C:\Users\Payton\Desktop>libusb-demo.exe Bus 003 Device 001 ID 0e0f:0003 Bus 001 Device 002 ID 0e0f:0002 Bus 003 Device 000 ID 15ad:0779 Bus 002 Device 000 ID 15ad:0770 Bus 003 Device 002 ID 0e0f:0002 Bus 003 Device 003 ID 0e0f:0002 Bus 001 Device 000 ID 15ad:0774</code></pre></li> </ul> <h1>参考</h1> <p><a href="https://zhuanlan.zhihu.com/p/507532813">Windows上Rust所依赖的msvc到底怎么装?</a></p> <p><a href="https://github.com/dcuddeback/libusb-rs/issues/20">Linking on Windows #20</a></p> <h1>附</h1> <ol> <li>项目目录 <img src="//www.pcdeng.com/uploads/rust-libusb-demo.jpg" alt="" /></li> </ol>
详情
<p>我之前写过一篇文章<a href="http://pcdeng.com/tauri-printer.html">《基于 Tauri + React + Rust + libusb + ESC/POS 驱动打印机蜂鸣器》</a>,有网友评论说:</p> <blockquote> <p>现在又卡在了link上,无法打开输入文件usb-1.0.lib</p> </blockquote> <p>然后我在自己的电脑也复现了,但之前安装环境是稀里糊涂地装了一遍,有些地方是不清楚原理的。</p> <p>趁着今天休息的时间,在家里的电脑重新装了一遍。我只记录了一些我疑惑的点。</p> <p>具体如何安装的软件见文章底部的<code>参考</code>部分列出的链接。</p> <h1>需要的软件</h1> <h2>Rust 工具集。</h2> <ul> <li><code>rustup</code> 是 Rust 工具链的安装程序和更新程序。</li> <li><code>Cargo</code> 是 Rust 包管理工具的名称。</li> <li><code>rustc</code> 是 Rust 编译器。大多数情况下,你不会直接调用 <code>rustc</code>,而是通过 <code>Cargo</code> 间接调用它。</li> </ul> <h2>MSYS2</h2> <blockquote> <p>Mingw(Minimalist GNU for Windows)是一个开发工具集,提供了用于 Windows 环境下编译和运行 C、C++ 等程序的工具链。</p> <p>Mingw-w64 是 Mingw 的一个分支,专注于支持 64 位 Windows 系统。它提供了对 64 位 Windows 环境下各种 API 的支持。</p> <p>Msys(Minimal System)是一个轻量级的类 Unix 环境,它提供了一些基本的 Unix 工具和 shell 环境,用于在 Windows 系统上运行 Unix 程序。</p> <p>Msys2 是 Msys 的一个升级版,它提供了更丰富的软件包管理系统和更新的工具链,使得在 Windows 系统上使用Unix 程序变得更加方便。</p> <p>Cygwin 是另一个类 Unix 环境,它提供了一套完整的 GNU 工具链和一些 Unix 系统调用的实现,使得在 Windows 系统上可以运行大部分的 Unix 程序。</p> <p>这些工具之间的关系是:</p> <p>Mingw 和 Mingw-w64 提供了用于编译和运行 C、C++ 等程序的工具链;</p> <p>Msys 和 Msys2 提供了在 Windows 系统上运行 Unix 程序所需的环境和工具;</p> <p>Cygwin 则提供了一套完整的 GNU 工具链和 Unix 系统调用的实现,使得在 Windows 系统上可以运行大部分的Unix 程序。</p> <p>Msys 和 Msys2 都是基于 Cygwin 开发的,但它们的目标不同,Msys 和 Msys2 更加轻量级且专注于提供基本的Unix 环境和工具,而 Cygwin 则更加完整和强大。</p> </blockquote> <h3>pkg-config</h3> <blockquote> <p>pkg-config 是一个用于管理编译和链接时的依赖库的工具。它可以帮助开发人员在编译和链接时自动查找和配置所需的库文件和头文件。</p> <h3>libusb</h3> <p>用于访问 USB 设备的跨平台库。</p> </blockquote> <p>最后来一张图:</p> <p><img src="//pcdeng.com/uploads/rust-toolchain.jpg" alt="" /> 安装好以上的软件后。</p> <h1>检查一下软件信息</h1> <h2>rust 相关</h2> <pre><code>$ rustup --version rustup 1.26.0 (5af9b9484 2023-04-05) info: This is the version for the rustup toolchain manager, not the rustc compiler. info: The currently active `rustc` version is `rustc 1.74.1 (a28077b28 2023-12-04) $ cargo --version cargo 1.74.1 (ecb9851af 2023-10-18) $ rustup toolchain list stable-x86_64-pc-windows-gnu (default) stable-x86_64-pc-windows-msvc</code></pre> <p>可以看出 rust 我是基于 <code>GNU 工具链</code> 来编译的。安装 <code>gnu 工具链</code>,可以通过命令 <code>rustup toolchain install stable-gnu</code>;</p> <p>如果要安装 <code>msvc 工具链</code>,则 <code>rustup toolchain install stable-msvc</code></p> <p>设置默认工具链 <code>rustup default stable-gnu</code></p> <h2>MSYS2</h2> <p><code>Mingw-w64</code> 所在目录:<code>C:\msys64\mingw64</code></p> <pre><code>$ pkg-config --cflags --libs libusb-1.0 -IC:/msys64/mingw64/include/libusb-1.0 -LC:/msys64/mingw64/lib/libusb-1.0/static -lusb-1.0</code></pre> <p>请确保你电脑安装好这些软件,如果没有,请参考<code>参考</code>列出的链接。</p> <h1>配置 libusb</h1> <p>这里主要参考 <a href="https://qianchenzhumeng.github.io/posts/rust_usb_programming/">Rust USB 开发|前尘逐梦</a></p> <ol> <li><a href="https://github.com/libusb/libusb/releases?page=3">下载 libusb</a> 编译好的包。我下载的是 <code>libusb-1.0.20.7z</code></li> <li>解压后,进入 <code>libusb-1.0.20</code> 目录。</li> <li>复制 <code>include</code> 下的 <code>libusb-1.0</code> 文件夹到 <code>C:\msys64\mingw64\include</code> 下。</li> <li><code>C:\msys64\mingw64\include</code> 下新建 <code>libusb-1.0</code>。</li> <li>复制 <code>MinGW64</code> 下的 <code>dll</code> 和 <code>static</code> 文件夹到 <code>C:\msys64\mingw64\include\libusb-1.0</code>。</li> <li><code>C:\msys64\mingw64\lib\pkgconfig</code> 下新建 <code>libusb-1.0.pc</code></li> <li><code>libusb-1.0.pc</code> 内容,静态链接 libusb</li> </ol> <pre><code>prefix=c:\msys64\mingw64 exec_prefix=${prefix} includedir=${prefix}/include/libusb-1.0 libdir=${prefix}/lib/libusb-1.0/static Name: libusb Description: libusb Version: 1.0 Cflags: -I${includedir} Libs: -L${libdir} -lusb-1.0</code></pre> <h1>测试验证</h1> <h2>创建 rust 项目</h2> <pre><code class="language-bash">cargo new libusb-demo cd libusb-demo cargo add libusb code .</code></pre> <h2>调用 libusb</h2> <pre><code class="language-rs">// src/main.rs extern crate libusb; fn main() { let context = libusb::Context::new().unwrap(); for device in context.devices().unwrap().iter() { let device_desc = device.device_descriptor().unwrap(); println!( "Bus {:03} Device {:03} ID {:04x}:{:04x}", device.bus_number(), device.address(), device_desc.vendor_id(), device_desc.product_id() ); } } </code></pre> <h2>运行</h2> <pre><code class="language-bash">$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target\debug\libusb-demo.exe` Bus 001 Device 002 ID 413c:2113 Bus 001 Device 001 ID 413c:301a Bus 001 Device 002 ID 0bda:c829 Bus 001 Device 000 ID 8086:7ae0</code></pre> <h1>参考</h1> <p><a href="https://zhuanlan.zhihu.com/p/655386777">Windows 上安装 Rust 及配置</a></p> <p><a href="https://learn.microsoft.com/zh-cn/windows/dev-environment/rust/">在 Windows 上通过 Rust 进行开发</a></p> <p><a href="https://qianchenzhumeng.github.io/posts/rust_usb_programming/">Rust USB 开发|前尘逐梦</a></p> <p><a href="https://blog.csdn.net/dorlolo/article/details/131009754">windows下快速安装gcc、pkg-config的方法</a></p>
详情
<h1>需求</h1> <p>网页通过蓝牙发送 TSPL 指令到打印机。</p> <h1>前置条件</h1> <ul> <li>电脑需要有蓝牙</li> <li>低功耗蓝牙打印机(关键是低功耗)</li> </ul> <p align=center><img src="/uploads/bluetooth-device.jpg" alt="image.png" width="300" /></p> <p align=center>表1</p> <ul> <li>了解蓝牙基础概念和通讯原理</li> </ul> <h1>概念</h1> <h2>Web Bluetooth API</h2> <p>网络蓝牙 API 提供了与蓝牙低功耗外围设备连接和交互的能力。Web Bluetooth API 现在还是一项实验性的功能,用于生产环境需谨慎。</p> <p><img src="uploads/web-bluetooth-compatibility.jpg" alt="image.png" /></p> <h1>实现</h1> <h2>1. 配对</h2> <pre><code class="language-js">const pair = async () => { return navigator.bluetooth .requestDevice({ filters: [{ services: [SERVICE_UUID] }], }) .then((device) => { selectedDevice = device; return device; }) .catch((err) => { console.log(err.message); return ""; }); };</code></pre> <h2>2. 连接 GATT server 并获取 打印特性</h2> <pre><code class="language-js">const getPrintCharacteristic = async () => { if (!selectedDevice) { return Promise.reject(new Error("没有配对设备")); } if (printCharacteristic) { return Promise.resolve(printCharacteristic); } return Promise.resolve(selectedDevice) .then((device) => { console.log(`设备名称 ${device.name}`); console.log("连接到 GATT 服务器......"); return device.gatt.connect(); }) .then((server) => server.getPrimaryService(SERVICE_UUID)) .then((service) => service.getCharacteristic(CHARACTERISTIC_UUID)) .then((characteristic) => { printCharacteristic = characteristic; return characteristic; }); };</code></pre> <h2>3. 发送命令</h2> <pre><code class="language-js">const sendPrinterData = (cmd) => { if (!printCharacteristic) { console.log("无法打印:打印特性属性为空"); return; } const encoder = new TextEncoder("utf-8"); const text = encoder.encode(cmd); return printCharacteristic.writeValue(text).then(() => { console.log("发送完毕"); }); };</code></pre> <h1>完整代码(仅供参考)</h1> <pre><code class="language-html"><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Web Bluetooth 示例</title> </head> <body> <button id="connectBtn">连接</button> <button id="sendBtn">发送</button> <script src="./bluetooth.js"></script> </body> </html></code></pre> <pre><code class="language-js">// 以下这两个值,在标准的 UUID 文档中找不到。应该是设备制造商没有按标准执行,我是从 https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-printer/index.html 获取的 const SERVICE_UUID = '000018f0-0000-1000-8000-00805f9b34fb'; // 打印服务的 UUID const CHARACTERISTIC_UUID = '00002af1-0000-1000-8000-00805f9b34fb'; // 写特性的 UUID let selectedDevice; let printCharacteristic; const sendPrinterData = (cmd) => { if (!printCharacteristic) { console.log("无法打印:打印特性属性为空"); return; } const encoder = new TextEncoder("utf-8"); const text = encoder.encode(cmd); return printCharacteristic.writeValue(text).then(() => { console.log("发送完毕"); }); }; const getPrintCharacteristic = async () => { if (!selectedDevice) { return Promise.reject(new Error("没有配对设备")); } if (printCharacteristic) { return Promise.resolve(printCharacteristic); } return Promise.resolve(selectedDevice) .then((device) => { console.log(`设备名称 ${device.name}`); console.log("连接到 GATT 服务器......"); return device.gatt.connect(); }) .then((server) => server.getPrimaryService(SERVICE_UUID)) .then((service) => service.getCharacteristic(CHARACTERISTIC_UUID)) .then((characteristic) => { printCharacteristic = characteristic; return characteristic; }); }; const handleSend = () => { getPrintCharacteristic() .then(() => { sendPrinterData("SELFTEST\r\n"); // 发送打印自检页 }) .catch((err) => { console.log("发送指令失败:", err.message); }); }; const pair = async () => { return navigator.bluetooth .requestDevice({ filters: [{ services: [SERVICE_UUID] }], }) .then((device) => { selectedDevice = device; return device; }) .catch((err) => { console.log(err.message); return ""; }); }; const handleConnect = async () => { const device = await pair(); console.log("配对设备:", device); }; const init = () => { const btn = document.getElementById("connectBtn"); btn.addEventListener("click", handleConnect); const sendBtn = document.getElementById("sendBtn"); sendBtn.addEventListener("click", handleSend); }; init();</code></pre> <h1>注意&问题</h1> <ol> <li>由于 Web Bluetooth 功能还在实验阶段,有些功能还需要打开特性开关,如 <code>Bluetooth.getDevices()</code> 接口,可以通过在浏览器地址输入 <code>chrome://flags/#enable-web-bluetooth-new-permissions-backend</code>,进入 <code>Experiments</code> 面板打开 <code>web-bluetooth-new-permissions-backend</code> 特性</li> <li><code>printCharacteristic.writeValue</code> 一次性只能最多发送 <code>512</code> 字节的数据,数据太大,比如发送图片,要分包发送,可以参考 <a href="https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-printer/index.html">demo</a>,但是有些打印机一次最多发送的字节数比 <code>512</code> 小,最好是连接的打印机获取一次最多能发送的字节数。</li> <li>传输数据很慢,我测试过几台打印机,发现打印图片超级慢。因为图片的数据太大了,也可能是打印机性能不好。具体性能瓶颈卡在哪里还不知道怎样排查,如果你有这方面的经验,欢迎评论。</li> </ol> <h1>参考</h1> <p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Bluetooth">https://developer.mozilla.org/zh-CN/docs/Web/API/Bluetooth</a></p> <p><a href="https://zhuanlan.zhihu.com/p/20657057">https://zhuanlan.zhihu.com/p/20657057</a></p> <p><a href="https://blog.csdn.net/tanqth/article/details/108151033">https://blog.csdn.net/tanqth/article/details/108151033</a></p> <p><a href="https://github.com/WebBluetoothCG/demos">https://github.com/WebBluetoothCG/demos</a></p>
详情
<h1>需求</h1> <p>网页通过 USB 发送 ESC/POS 指令到打印机。</p> <h1>概念</h1> <h2>什么是 WebUSB?</h2> <blockquote> <p>WebUSB 是一个 Web API,它允许网页通过 USB 连接与本地的 USB 设备进行通信。通过 WebUSB,网页可以与各种类型的 USB 设备进行直接交互,而无需通过平台特定的驱动程序或中间件。</p> <p>使用 WebUSB,开发人员可以创建具有以下功能的网页应用程序:</p> <ul> <li>识别和连接可用的 USB 设备。</li> <li>与 USB 设备进行数据交换,包括读取和写入设备的数据端点。</li> <li>监听 USB 设备上的事件,例如设备连接和断开连接。</li> </ul> <p>由于 WebUSB 使用了 USB 设备的通用性标准,因此它可以与各种类型的设备进行通信,例如打印机、扫描仪、键盘、鼠标、游戏控制器等。这使得开发人员可以创建具有更高级别的交互和控制的 Web 应用程序,而无需依赖于特定平台或操作系统。</p> <p>需要注意的是,为了保护用户安全和隐私,WebUSB 需要用户的明确授权才能访问 USB 设备。用户将通过浏览器的权限提示决定是否允许网页应用程序与指定的 USB 设备进行通信。</p> </blockquote> <h2>什么是 ESC/POS 指令?</h2> <blockquote> <p>ESC/POS(Epson Standard Code for Printers)是一种打印机指令集,由爱普生(Epson)公司创建并广泛使用。ESC/POS 指令集包括一系列控制命令,以控制打印机的各种操作,例如打印文本、绘制条形码、切纸等等。它被应用到广泛的打印机应用程序中,例如收银系统、票据打印、咨询机等等。</p> <p>ESC/POS 指令集为打印机提供了许多有用和高级的特性。例如,它支持各种字体和字号、颜色、对齐方式、旋转、加粗、下划线、倾斜等功能,以及各种类型的条形码和二维码。此外,ESC/POS 指令集还支持自定义 logo 和图像,以及打印多个副本和自动切纸等功能。</p> <p>ESC/POS 命令是通过向打印机发送 ASCII 字符串来实现的。对于不同的打印机,其 ESC/POS 指令集可能非常相似,但也存在一些差异。因此,开发人员应该了解所使用打印机的文档以正确使用其 ESC/POS 指令集。</p> </blockquote> <h1>实战</h1> <p>流程:获取设备(配对,或者从已配对的列表中获取)-> 初始化(打开设备,选择配置,声明接口)-> 发送 ESC/POS 命令</p> <h2>配对</h2> <pre><code class="language-js">const getDevice = async () => { const device = await navigator.usb .requestDevice({ filters: [] }) .then((device) => { return device; }) .catch(() => { return null; }); return device; };</code></pre> <h2>初始化设备</h2> <pre><code class="language-js"> const initDevice = async (device) => { await device.open(); const { configurationValue, interfaces } = device.configuration; await device.selectConfiguration(configurationValue || 0); await device.claimInterface(interfaces[0].interfaceNumber || 0); return device; };</code></pre> <h2>发送 ESC/POS 指令</h2> <pre><code class="language-js">const sendCmd = async (device) => { const cmd = new Uint8Array([0x1f, 0x1b, 0x1f, 0x67, 0x00]); const { outEndpoint } = getEndpoint(device); device.transferOut(outEndpoint, cmd); };</code></pre> <h1>完整代码</h1> <pre><code class="language-html"><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebUSB 驱动打印机打印</title> </head> <body> <button id="connectBtn">连接</button> <button id="sendBtn">打印自检页</button> <script src="./usb.js"></script> </body> </html></code></pre> <pre><code class="language-js">let selectedDevice; // 当前选择的设备 const getEndpoint = (device) => { let inEndpoint = undefined; let outEndpoint = undefined; for (const { alternates } of device.configuration.interfaces) { const alternate = alternates[0]; const USB_PRINTER_CLASS = 7; if (alternate.interfaceClass !== USB_PRINTER_CLASS) { continue; } for (const endpoint of alternate.endpoints) { if (endpoint.type !== "bulk") { continue; } if (endpoint.direction === "in") { inEndpoint = endpoint.endpointNumber; } else if (endpoint.direction === "out") { outEndpoint = endpoint.endpointNumber; } } } return { inEndpoint, outEndpoint, }; }; const connect = async () => { let device = await getDevice(); if (!device) { return; } selectedDevice = device; await initDevice(device); }; const getDevice = async () => { const device = await navigator.usb .requestDevice({ filters: [] }) .then((device) => { return device; }) .catch(() => { return null; }); return device; }; const initDevice = async (device) => { await device.open(); const { configurationValue, interfaces } = device.configuration; await device.selectConfiguration(configurationValue || 0); await device.claimInterface(interfaces[0].interfaceNumber || 0); }; const sendCmd = async () => { if (!selectedDevice) { console.warn("请先配对设备"); return; } const cmd = new Uint8Array([0x1f, 0x1b, 0x1f, 0x67, 0x00]); const { outEndpoint } = getEndpoint(selectedDevice); selectedDevice.transferOut(outEndpoint, cmd); }; const init = () => { navigator.usb.addEventListener("connect", (e) => { console.log("新连上的设备", e.device); }); navigator.usb.addEventListener("disconnect", (e) => { console.log("断开的设备", e.device); }); const connectBtn = document.querySelector("#connectBtn"); connectBtn.addEventListener("click", connect); const sendBtn = document.querySelector("#sendBtn"); sendBtn.addEventListener("click", sendCmd); }; document.addEventListener("DOMContentLoaded", init);</code></pre>
详情
<p>最近项目需要生成条码,展示的条码字符需要支持自定义字体。</p> <p>实现其实好简单,翻看了一下 bwip-js 的文档就知道。我这里只想简单记录一下。</p> <p>我的项目依赖:</p> <ul> <li>React 18.2.0</li> <li>bwip-js 4.1.2</li> <li>antd 5.8.6</li> <li>fabricjs 5.3.0</li> </ul> <h1>先上效果:</h1> <p><img src="/uploads/bwip-js-load-font.png" alt="" /></p> <h2>再上代码:</h2> <p><code>App.tsx</code> (只包含关键部分)</p> <h2>1、导入</h2> <pre><code class="language-tsx">import { code128, loadFont } from "bwip-js/browser";</code></pre> <h2>2、添加条码</h2> <pre><code class="language-tsx">const board = new fabric.Canvas("board", { width: window.innerWidth - 160 - 200, height: window.innerHeight - 50 - 40 - 2 - 20, backgroundColor: "#fff", }); /** * 加载自定义字体 * * fontName 和 fontUrl 一般从字体列表获取而字体列表一般通过 API 获取。 * * @TODO 优化:记住加载过的字体,对同一个字体不需要重复下载,因为一般字体文件比较大,有些会达到 10M */ const loadCustomFont = async (fontName: string, fontUrl: string) => { const arrayBuffer = await fetch(fontUrl).then((res) => res.arrayBuffer()); const bytes = new Uint8Array(arrayBuffer); loadFont(fontName, bytes); }; /** * 添加条形码 */ const onAddBarcode = async () => { const fontName = "迷你简丫丫体"; // 这个名字不重要,重要的是下面的 ttf 文件, 4d44c61fbfb84e279b28adf1e1eb9b40.ttf 对应的字体就是“迷你简丫丫体” await loadCustomFont(fontName, "./4d44c61fbfb84e279b28adf1e1eb9b40.ttf"); const canvasEle = document.createElement("canvas"); const canvas = code128(canvasEle, { bcid: "code128", text: "遥遥领先", includetext: true, textfont: fontName, textyoffset: 3, }); const base64 = canvas.toDataURL("jpg", 1); const imgEle = await loadImg(base64); const fabricImg = new fabric.Image(imgEle); fabricImg.on("resizing", (evt) => { console.log(evt); }); fabricImg.on("scaling", (evt) => { console.log(evt); }); board.add(fabricImg); }; return <canvas id="board"></canvas></code></pre> <h1>参考</h1> <p><a href="http://bwip-js.metafloor.com/">http://bwip-js.metafloor.com/</a></p>
详情