仓颉语言GUI开发实战:用仓颉写串口/网络二合一调试助手

这篇记录下使用仓颉语言开发界面GUI软件, cj-link 调试助手的完整来路:为什么要写一个调试助手、两条链路(串口与网络)各自怎么落、探针自检到底验了什么,以及我在 Windows 实机上是怎么被一处「读串口拿不回字节」逼到 C 桥里去的。

一、缘起:我想验证的是「能碰硬件的框架」

cj-tauri 是我用仓颉语言做的 Tauri 2 对应物。写完力扣那个示例之后,我手上已经有了一块「能编译、能判题、能联远端」的靶子,但它有个共同点:全是纯软件链路 ------文件、进程、HTTP。框架真正没被压过的一类,是宿主能力:窗口、菜单、托盘、拖放,还有串口。

串口恰好是最难糊弄的一种。它不像网络那样能在同一台机器上开个 echo 服务器自娱自乐------有没有真的打开那个端口、有没有真的把字节写出去、驱动到底有没有把数据交回来 ,这三件事一旦有一件是假的,工具就毫无用处。拿它当靶子的好处是:验收标准天然是"线路上真的流过这些字节",而不是"命令返回了 ok"。

所以我想要一个东西:一个窗口里同时有串口和网络两条链路,共用一块收发日志 。串口用于对板子、对模块;网络用于对远端服务、对本机做 TCP/UDP 联调。这类工具不新鲜(串口有 SSCOM / XCOM,网络有各种网络调试助手),正因为不新鲜,它才是一块好的试金石:所有功能都有明确的"正确"参照物。

二、先说清楚 cj-tauri 是什么

后面所有内容都建立在这一句话上:cj-tauri 是用仓颉语言实现的 Tauri 2 对应物 ------把 Tauri 的三件套在仓颉里独立实现了一遍:WebView 宿主 + IPC 双向桥 + 能力安全模型 。前端用任意 Web 技术(HTML/CSS/JS,另有 Vue 3 / React 18 模板),后端用仓颉静态编译,产物是不带 Chromium 的原生桌面应用:Windows 走 WebView2、Linux 走 WebKitGTK,鸿蒙 ArkWeb 是架构预留位。

cj-tauri 项目开源地址: https://atomgit.com/qq8864/cj-tauri

跟"换个语言重写一遍"相比,真正让我觉得有意思的是仓颉这门语言的形状把几件事推到了前台:

  • 平台差异只允许出现在两处 :仓颉侧的 @When 条件编译,和 C 桥的 native/bridge_*.c。IPC、能力校验、命令分发必须跨平台共用------所以这个示例的串口在 Windows 与 Linux 上是同名同签名 的 cj_bridge_serial_*,上层一行平台分支都没有。这不是洁癖,是因为我在下面第八节真的改了一次平台层,而改完之后两个平台的表现同时对了。
  • 安全模型是"能力清单 + 三处联动" :一个命令要能调,得同时做到「实现 handler」「app.register(...) 注册」「在 capabilities/*.json 里声明」。默认最小权限,漏一处分别得到 command not registered 或 command not allowed。
  • 命令是异步分发的 :校验在调用线程上同步做,通过的命令 spawn 到 worker 线程执行、结果回投前端。串口读写这类会阻塞的调用因此不会冻住窗口。
  • 能力位如实上报 :宿主支持什么由 C 桥按编译期开关声明,system:host 会如实报 menu=1 tray=1 这样的位。做不到的东西不会假装做到。

这个示例的装配长这样(src/main.cj,143 行全部在内):

复制代码
TauriApp()
  .window(WindowConfig{ title: "cj-Link · 串口/网络调试助手", size: 1180x780 })
  .register("link", LinkCommands())      # 一个处理器带 7 个 link:* 命令
  .plugin(SerialPlugin())                # 官方 serial 插件:serial:list/open/read/write/close
  .plugin(StorePlugin())                 # store 插件:面板配置落 store.json
  → app.run(内嵌页面优先、否则读 ui/index.html)

能力清单 capabilities/default.json 一共放行了 10 个命令与 2 个事件:

复制代码
commands:  system:version  link:serialList  link:netConnect  link:netSend  link:netClose
           link:netStatus  link:crcCalc  link:ping  report  quit
permissions:  serial:default  store:default      ← 命名权限集,展开后与明文命令同权
events:       link:tcpData  link:tcpState

report / quit 是给探针自检用的辅助命令(第九节会讲它们凭什么算"证据"),permissions 那两行是插件的命名权限集 ------插件声明"我提供 serial:default 这个集",清单引用它才生效;集声明 ≠ 放行,这条和插件的铁律一致。

怎么把它装起来用

两条路,看你想干什么。

A. 起一个新工程(有 Node 环境即可拿到 CLI):

bash 复制代码
npx cj-tauri create myapp     # 脚手架;另有 --template vue|react
cd myapp
npx cj-tauri dev              # 编译并起窗口

B. 跑仓库里的示例(这篇讲的就在这里):

bash 复制代码
git clone https://atomgit.com/qq8864/cj-tauri.git && cd cj-tauri
cd native && ./build_linux.sh              # 先编 C 桥;Windows 用 native\build_win.bat
                                           # 产物必须落在 native/ 根,cjpm.toml 按那个路径找库
cd ../examples/cj-link && cjpm build       # 需要仓颉 SDK 1.2.0(含 stdx)进 PATH
./run.sh                                   # Linux;Windows 用 run.bat

去哪儿找它

想找什么 地址
仓库(主托管,双推 AtomGit + GitHub) atomgit.com/qq8864/cj-tauri
官网(中英双语,源文件在 website/) yangyongzhen.github.io/cj-tauri
npm(CLI 与脚手架) npmjs.com/package/cj-tauri
上手文档 README.md(概览与示例一览)、docs/使用文档.md(从零到窗口)
设计取舍 docs/设计决策记录.md(每个长期决策的「为什么」与真实代价)
开发契约与坑清单 AGENTS.md(这篇里的坑大半也在那儿有一份)

三、它长什么样

窗口 1180×780,左边一列参数、右边一大块日志(标题栏写着「cj-Link · 串口/网络调试助手」):

复制代码
┌────────────────────────────────────────────────────────────────────────┐
│ cj-Link v0.8.0                                        ● 未连接           │
│ 串口 / 网络二合一调试助手 · cj-tauri                                     │
├───────────────────┬────────────────────────────────────────────────────┤
│ [ 串口 ] [ 网络 ]  │ 收发日志 [全部][接收][发送][系统] 自动滚动:开 视图:双显 清空│
├───────────────────┼────────────────────────────────────────────────────┤
│ 串口               │ 00:02:00.062  ·  cj-link 就绪(等待连接)             │
│ 端口  [COM1   ↻]   │ 00:02:00.105  ·  串口枚举完成: 2 个端口               │
│ 波特率 [115200 ▾]  │                                                    │
│ 数据位 [8 ▾]       │                                                    │
│ 校验位 [无 ▾]      │                                                    │
│ 停止位 [1 ▾]       │                                                    │
│ [   打开串口   ]   │                                                    │
│ ─────────────────  │                                                    │
│ 校验计算           │                                                    │
│ [01 03 00 00 ...][算]│                                                    │
│ ─────────────────  │                                                    │
│ 发送队列           │                                                    │
│  暂无可条目        │                                                    │
└───────────────────┴────────────────────────────────────────────────────┘

日志那一栏是这个工具的主体:HEX/ASCII 双显 (双显 / 仅 HEX / 仅文本三档)、方向分色

(↑TX 黄 / ↓RX 绿 / ·系统灰)、毫秒时间戳、按方向过滤、TX/RX 字节与错误计数。中间那排按钮是给

"盯着看波形"用的------调试协议的时候,一眼分清"我发的"和"它回的"比什么都重要。

代码结构也一样平铺直叙,没有任何特殊之处:

复制代码
src/main.cj        143 行   装配:link 处理器 + serial/store 插件 + 探针注入
src/commands.cj    879 行   7 个 link:* 命令(网络会话 / 校验 / 枚举 / ping)
ui/index.html      845 行   整页前端(参数面板 + 收发日志 + 发送区)
src/packed_*.cj            打包存根与 base64 资源装配(第九节)
capabilities/default.json  10 个命令 / 2 个事件 / 2 个命名权限集

前端页面为什么独立成 ui/index.html,不内联进仓颉的三引号字符串? 这个示例的 JS 里

"反斜杠"和"${}"到处都是(正则、\n 拼接、模板字面量)。而仓颉的三引号字符串会处理反斜杠转义 、

${...} 是活插值 ------把这份 JS 塞进 """ 里,等于让它先过一遍仓颉的词法分析:\n 变成真换行、

把 JS 字符串或单行注释当场拆断,整个 <script> 语法错误,页面里一行都不执行而 stderr 毫无提示 。

我在别的示例上被这个坑咬过一次,所以这个示例从一开始就把页面放成独立文件:源码树里改完刷新即生效,

打包的时候再由资源装配层把它读进二进制。

四、串口这条链路

枚举 走 link:serialList(统一出口):Windows 侧是 C 桥扫 \Device\VSerial_* 这类设备名,

Linux 侧扫 /dev/tty*,出来的都是"人话"端口名。面板上那个 ↻ 就是重新枚举------插上 USB 转串口之后再点它。

打开 是 serial:open,带上波特率/数据位/校验位/停止位(默认 115200 8N1)。这里有个实际行为值得

先说清楚:打开是独占的 ,所以"打不开"通常不是代码问题,而是别的程序占着这个口------

这恰恰是调试助手这类工具的日常困境,页面上会如实把错误抛回来。

收 走的是前端轮询 serial:read:

js 复制代码
// ui/index.html(节选)
t().invoke('serial:read', { handle: S.serHandle, maxBytes: 1024, timeoutMs: 200 })
// 拿到数据 → 60ms 后再来一轮;空手回来 → 退到 500ms

这段"拿到就快、空手就慢"的节奏不是一开始就这么写的------它其实是第八节那个 bug 逼出来的产物:

修复前那个 timeoutMs: 200 根本没人理 ,serial:read 只会把"此刻恰好躺在缓冲里"的字节交回来,

所以页面只能用高频空轮询去碰运气。修好之后这个参数才真正生效,页面的轮询节奏也就可以松弛下来。

发 是 serial:write:HEX 或文本两种模式、可选追加 CRLF、也能挂定时发送(周期 50ms--60s)。

五、网络这条链路:一个命令三种模式

网络侧对外只有一个命令 link:netConnect,用 mode 参数分三种模式------这不是为了省命令,

而是因为这三种模式对使用者是同一件事("连出去/听进来"),差异在参数上:

mode 做什么 本示例的边界
tcp-client 连一个对端,5s 建连超时 调试远端服务用
tcp-server 本机监听,accept 多客户端(上限 16),每客户端一条读线程;发送缺省广播,可按通道 #id 定向 对端主动连上来时用(板子/设备回连)
udp connect 到对端后 send/receive(数据报语义) 无连接场景;纯 bind 收报是二期

收统一走宿主事件推送,前端只需要接一次:

js 复制代码
__CJ_TAURI__.listen('link:tcpData', function (ev) {
  // 报文自带 mode / channel / from:TCP 客户端、TCP 服务器某个通道、UDP
  // 都从这一个出口进来,前端按这三个字段决定怎么显示
})

配套还有一条 link:tcpState 报连接状态变化。为什么串口是轮询、网络是推送? 因为 TCP 这边有

可等待的 socket,仓颉侧的读线程能一直挂着等;而串口在 Windows 上没有等价的"数据到"事件源,

与其在 C 桥里再塞一根线程,不如让页面按节奏问------两种收法落在同一份命令契约之下,

前端只多接一个 listen,没有第二种心智模型。

六、校验计算:把协议逻辑放在后端

link:crcCalc 是这个示例里最"不界面"的一块:CRC16-Modbus(低前 / 高前两种字节序)、CRC16-CCITT、

累加和、异或和、LRC,外加一键把"整帧 + CRC"回填到发送框。

它在仓颉侧而不是在 JS 里,理由很直接:这是协议逻辑,算错了串口上发出去的就是废帧 ,

而废帧在日志里看起来和正常帧一模一样。所以我给它配了标准向量:

复制代码
输入  01 03 00 00 00 02
输出  c40b                      (CRC16-Modbus,低字节在前)

这条向量现在是探针自检里的一条断言------我不信"看起来对"的 CRC,界面上的计算按钮只是调用它。

七、探针自检:11 步,以及"线路上真收到过什么"

调试助手这类工具最容易糊弄自己的地方,是它自己说它成功了。所以这个示例的自检不是"点一遍看有没有报错",而是:

bash 复制代码
CJ_LINK_PROBE=1 CJ_LINK_PROBE_SERIAL_PATH=COM1 \
CJ_LINK_PROBE_TCP_HOST=127.0.0.1 CJ_LINK_PROBE_TCP_PORT=15021 CJ_LINK_PROBE_UDP_PORT=15022 \
  ./target/release/bin/main 2> app.log

置上 CJ_LINK_PROBE=1 之后,宿主会在 document-start 阶段给页面注入一段预执行脚本(早于页面自己的

脚本执行),页面于是自动跑完 11 步:

复制代码
串口     open → write → read → close
校验     CRC16-Modbus 标准向量 01 03 00 00 00 02 → c40b
TCP      connect → send → recv → close
UDP      connect → send → recv → close

每一步的结果经 report 命令回投到 stderr ------这里必须走 stderr:仓颉 println 的 stdout 有缓冲,

进程被强杀时那几行日志会丢,而桥的 stderr 是每行即时落盘的。run.sh(Linux)与 run.bat selfcheck

(Windows)就是拿这份日志判绿的,口径都一样:数 PASS 明细、确认没有 FAIL 行。

但真正让我踏实的是对端那一侧。对端不是页面里某个 mock,而是独立跑在应用之外的进程:

  • Linux:cj-link-peer.py 用 pty 扮演串口(回什么由它决定)+ TCP echo + UDP echo;
  • Windows:没有 pty ------所以我把它拆成两个进程:link-peer-win.py 管 TCP/UDP echo,
    link-serial-echo.ps1 用 System.IO.Ports 占住 COM2「收到什么回什么」。

于是验收凭证落在应用进程之外:

复制代码
对端日志(串口)  rx 12 bytes: cj-link-ping
对端日志(TCP)   rx 16 bytes
对端日志(UDP)   rx 16 bytes
应用侧           11/11 PASS

应用说"我发了"不算数,对端说"我收到了"才算。 这条规矩在别的示例上已经救过我一次

(发后即忘的接口返回 0 却什么都没发出去),串口这种"没有回执就没有真相"的链路上更是必须。

Windows 这边本机正好有一对互通的虚拟串口:COM1 ↔ COM2 (注册表里 \Device\VSerial_0 / _1),

枚举出来就是两个端口,对端拿一个、应用拿一个------所以"串口链路"在 CI 似的无人值守脚本里也能真跑通,

不需要真实硬件。

八、实机上折腾我的三件事

8.1 读串口拿不回字节:真凶在平台层,不在页面

第一轮探针只过了 10/10 。少的那一条很具体:串口那步的 rx text= 与 close 两条明细凭空消失 ,

serial:read 交回 0 字节。

按常理先怀疑上层:是不是前端轮询时序不对、是不是插件命令顺序有问题。查了一圈不成立------因为

serial:read 返回得很"干脆":它没超时、没报错,就是立刻说"没有数据"。

这时候最省事的做法是写一个几十行的 C 程序,绕开整个框架,直接对 Windows API 打同样的参数:

c 复制代码
// 与桥里打开串口时同一套超时参数
SetCommTimeouts(h, &ct);   // ReadIntervalTimeout = MAXDWORD;两个 total 全 0
ReadFile(h, buf, n, &got, &ov);

打印出来的是:

复制代码
phase1: ReadFile ok=1 got=0 err=0 elapsed=0 ms      ← 预算写的是 1000ms

0 毫秒就回来了。 这套超时参数(ReadIntervalTimeout=MAXDWORD + 两个 total 全 0)在 Windows 上是

"立刻交回已有字节"语义:缓冲里没数据时,驱动当场把请求完成成 0 字节、ReadFile 返回 TRUE------
于是 C 桥里那条"等 OVERLAPPED 事件、最多等 timeout_ms"的分支
根本进不去
。

前端传的 timeoutMs: 200 从第一天起就形同虚设:它只能拿到"此刻恰好已经躺在缓冲里"的字节,

其余时候靠高频空轮询碰运气。

修法是把预算按每次调用下发给驱动,而不是"打开时设一次":

c 复制代码
ct.ReadIntervalTimeout = 0;
ct.ReadTotalTimeoutConstant = 预算;    // 有首字节立刻完成,没有就等满预算
ct.ReadTotalTimeoutMultiplier = 0;
SetCommTimeouts(h, &ct);               // 每次 read 之前

这样语义就和 Linux 那边的 poll 对齐了(Linux 侧本来就是按预算等),只有预算为 0 时才回到

"立即返回"那套。改完重建 C 桥,探针 10/10 → 11/11,串口那步拿回的正是对端回的字节:

复制代码
serial:read  hex=636a2d6c696e6b2d70696e67        (= "cj-link-ping")

这里我想强调的不是这个具体的 API,而是两条通用教训:

  1. 凡"声称按预算等待"的宿主调用,都要用一个真的没数据的时间窗去打它。 有数据的路径永远正常------
    它是靠"有数据"蒙过去的。要证伪它,就必须在明确的空窗 里测:预算 200ms 的调用必须在 200ms
    左右回来,而不是 0ms。
  2. 平台语义差异要被解释成"这个平台为什么这么做",而不是"这个平台坏了"。 ReadIntervalTimeout=MAXDWORD
    本身是合法且常见的写法(异步读的经典配置),它的错在于和"我要等满预算"这个意图不匹配 。
    把意图写进每次调用,比在打开时"设一次指望一直有效"稳。

8.2 打包第一次失败:看着像"框架缺依赖"

第一次跑单文件打包,报的是:

复制代码
error: root package 'cjTauri' imports package 'stdx.encoding.json' in its source code,
       but the dependency info is missing

指向的是框架包 ,字面意思像"框架忘了声明依赖"。但这个错只在打包场景出现、在示例目录 cjpm build

时从来不出现------所以我先去看了打包脚本干了什么:它在 dist-single/work/ 里自建一份工程副本 再编译,

应用副本的 stdx 路径是写死的绝对路径,而框架包(path = ../.. 指回仓库根)仍然按框架自己的
cjpm.toml
展开 ${CANGJIE_STDX}。

结论:环境变量没导出。打包器不会替你设,因为那是"你本机的 stdx 装在哪"这件事。导出之后一次过:

bash 复制代码
export CANGJIE_STDX=/d/cangjie-stdx/windows_x86_64_cjnative/dynamic/stdx
bash scripts/pack-win-single.sh examples/cj-link

这条也进 AGENTS.md 了------这类"报错指向的名字不是真原因"的坑最费时间,值得留档。

8.3 我第一张"界面正常"的截图,是别人的窗口

打包完之后我抓了张图想确认界面没问题。图看起来完全正常 :布局对、配色对、数据也对。

但它不是这个应用的窗口------是我屏幕上压在那片坐标上的另一个程序。

原因是抓图脚本用的 SetForegroundWindow 被系统静默拒绝了 (它连返回值都不看),

而 CopyFromScreen 抓的永远是"那一刻压在这片坐标最上面的窗口"。更隐蔽的是,窗口当时有一角在屏幕外,

那片坐标拍到的也是屏外的内容。

可靠做法是先 ShowWindow(SW_RESTORE) + SetWindowPos(hwnd, HWND_TOPMOST, ...) 把目标顶到最前、

并且挪进屏内 ,抓完再 HWND_NOTOPMOST 还原。最后的判据写成了土话:

拍完先自己看一眼图里是不是这个窗口 (标题栏、自己的 UI),别看命令的退出码------

退出码 0 在这件事上什么都不证明。

九、免 SDK 分发:一个 exe

示例本身能直接从源码跑(run.bat / run.sh),但想把它丢给"没装仓颉 SDK 的同事"时,它还能再往前一步:

bash 复制代码
export CANGJIE_STDX=/d/cangjie-stdx/windows_x86_64_cjnative/dynamic/stdx
bash scripts/pack-win-single.sh examples/cj-link
# => examples/cj-link/dist-single/cj-link.exe

产物是一个不带任何随行文件的 exe,本机实测:

复核项 实测值
体积 5,745,664 字节(约 5.5 MB)
子系统 Subsystem=00000002(GUI------双击不会弹控制台黑窗)
导入表 8 个 DLL,全是 Windows 系统库:msvcrt / KERNEL32 / WS2_32 / SHELL32 / dbghelp / comdlg32 / USER32 / ole32
WebView2Loader 内嵌在 exe 里 (162,392 字节),首次运行释放到 %TEMP%\cj-tauri-loader\

页面、能力清单、vendor 全在二进制里,双击就开(仍然需要系统有 WebView2 Runtime,那是 Windows 自带的)。

和力扣那个包最大的区别是:这个包不需要 SDK 就能全功能使用 ------调试助手本身不编译任何代码,

它用的全是系统能力(串口 API、socket、WebView2)。所以这个 exe 是真正"拿走就能用"的。

包内我也完整验了一遍:在隔离目录 里跑(独立的工作目录 + 独立的 WebView2 用户数据目录,

不碰你机器上那份 store.json),串口枚举 2 个端口、探针 11/11 、

串口 / TCP / UDP 三条链路全活,stderr 里没有一条 command not allowed 或 command not registered。

打包范式本身不值一提(就是 AGENTS 里那套「packed_assets.cj 空存根 + packed_resources.cj 资源装配」),

但有个设计选择值得点一下:页面读的是"内嵌优先、否则读盘" 。

源码树里改 ui/index.html 立即生效(不用重新打包),打完包之后同一份代码又变成"读二进制里的那份"------

一套代码两种运行方式,而不是维护两份页面。

十、写在最后

回头看,这个示例做的其实是三件事。

一是证明"能碰硬件的宿主能力"是真的。 串口这条链路是 枚举 → 打开 → 写 → 读 → 关闭,

Windows 与 Linux 走同名同签名 的 C 桥导出,上层一行平台分支都没有。最直接的证明就是第八节那次修复:

我只改了 Windows 平台层的读实现,Linux 那侧一个字没动------平台差异收敛到一处 这件事,

在被真实 bug 打一次的时候才显出价值。

二是把"证据"这件事做实。 探针自检的 11 步回投到 stderr,而对端的日志是另外一个进程写的:

应用说"我发了"不算,对端说"我收到了 12 字节 cj-link-ping"才算。同一套思路在 Linux 侧是 run.sh,

判绿口径一样(数 PASS 明细、确认没有 FAIL 行)。

三是留下了可核对的数字 :探针 11/11(串口 / CRC 标准向量 / TCP / UDP)、

单文件产物 5,745,664 字节、导入表 0 个非系统 DLL、GUI 子系统、本机 COM1↔COM2 真线往返。

最后是几句实话,免得期待错位:

  • 串口与网络是单链路互斥的 :同一时刻只用一条,页面上有提示。「串口↔网口透明互转」
    (把串口收到的直接转到网上)是我有意不做的二期------那已经不是一个"调试助手"该有的语义了;
  • TCP 服务器面向调试场景 :最多 16 个客户端、发送缺省广播,多客户端接进来之后的通道管理界面
    也在二期(命令层已经支持 link:netSend 带 channel 定向,缺的是前端控件);
  • UDP 只做了 connect 型客户端(send/receive),纯 bind 收报是二期;
  • 平台实测以 Windows(WebView2) 为主,Linux(WebKitGTK) 侧本示例是通的
    (run.sh 那 33 条断言就是它的验收),鸿蒙 ArkWeb 目前仍是架构预留位------我不会把没跑过的说成跑过。

想拿它当工具:装好 SDK,cd examples/cj-link && cjpm build,然后 run.bat(Windows)/ run.sh(Linux);

不想装 SDK 就直接双击 dist-single/cj-link.exe。想拿它当 cj-tauri 的样本,建议从两处读:

src/commands.cj 里的 netConnect(一个命令三种模式怎么落)、

native/platforms/windows/bridge_win.c 里那个 cj_plat_serial_read(第八节修的那处,

它把"宿主 API 的预算语义要按次下发"讲得比这段文字清楚)。

相关链接

  • 仓库(主托管) :atomgit.com/qq8864/cj-tauri
  • 官网 (中英双语):yangyongzhen.github.io/cj-tauri
  • npm (CLI 与脚手架):npmjs.com/package/cj-tauri
  • 这个示例 :examples/cj-link
    (含 README、探针脚本、Windows 侧对端脚本与打包存根)
  • 单文件 exe :examples/cj-link/dist-single/cj-link.exe------随仓库分发,不装 SDK 也能全功能使用
  • 同系列的坑与规矩 :AGENTS.md §4(这份坑清单里已经攒了不少同类条目,
    这篇里的串口读、打包环境变量、抓图三件事都在其中)
  • 同类示例 :examples/modbus-poll(Modbus 调试工具,对标 Modbus Poll)、
    examples/leetcode-gui(判题 GUI,见《仓颉版力扣 GUI 开发记》)、
    examples/markdown-notes、examples/typing-poem、examples/music 等,全部在 examples/ 下
相关推荐
持敬chijing1 小时前
白盒测试与代码审计-JS 数据逆向
javascript·网络·安全·web安全·网络安全·网络攻击模型·安全威胁分析
吴声子夜歌1 小时前
Docker入门与实战——高级网络功能
网络·docker
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】ChatServer HTTP接口模块实现总览
网络·c++·人工智能·网络协议·学习·http·架构
YH_Analytics2 小时前
2026 洁净室环境在线监测系统行业排名与份额对比销量与份额解析
大数据·网络·人工智能
Chenyu_3102 小时前
NUMA 基础:从 CPU、内存到 Node 的整体理解
linux·服务器·网络·缓存
对空六课3 小时前
两个统计平台数据不同:用同一时间窗和事件样本做差异定位
大数据·前端·网络·数据库
User_芊芊君子3 小时前
【JavaEE初阶 - 网络原理】网络分几层?从自定义协议聊到 DNS
java·网络·java-ee
2603_9695792810 小时前
备份最常用的三个软件是什么软件
网络
hqzing10 小时前
把 glibc 移植到鸿蒙 PC 上:它能用来做什么
华为·harmonyos