Java 调用扫描仪实战:基于 JNI 与 jtwain 的 TWAIN 桥接方案

简介:这份资源是一套用Java控制扫描仪的完整实现方案,面向需要在Java项目中集成扫描功能的开发者,尤其适合受限于浏览器或系统弹窗、希望静默调用扫描设备的场景。其核心思路是用C++编写底层扫描程序并封装为jtwain.dll,再由Java通过JNI调用该动态库完成扫描,且支持中文、扫描时不弹出扫描对话框,可免费使用。压缩包共32个文件、约205KB,包含8个java源码与13个class字节码、1个dll动态库、1个jar包,以及doc说明文档、ini配置、prefs与project工程文件等,覆盖源码、编译产物与工程配置,便于直接导入IDE阅读与二次开发。目前已有926人学习下载。读者可从中获取JNI调用DLL的完整范例、扫描参数配置思路与工程目录组织方式,快速理解Java与本地库协作的落地细节,并在此基础上改造出符合自身业务需求的扫描模块。

1. Java 接扫描仪这件事,为什么绕不开 JNI 和 jtwain

很多做企业文档系统的开发者第一次接到“Java 调扫描仪”的需求时,都会下意识觉得这活儿不难——不就是调个外设吗?真上手才发现,JDK 从诞生到现在,标准库里压根没有扫描仪这一层抽象。打印机还能靠javax.print糊弄一下,扫描仪连个官方 API 都没有。原因也简单:扫描协议在 Windows 上主要走 TWAIN,在 Linux/macOS 上走 SANE,两套东西都是 C/C++ 的原生接口,Java 想碰它们,只能通过 JNI 这座桥。

这份资源就是围绕这个痛点来的:一套用 Java 编写的扫描仪程序,核心思路是用 JNI 封装 TWAIN 协议,再通过 jtwain 这类桥接库把原生能力暴露给 Java 层。它解决的不是“扫描一张图”这么简单的事,而是让 Java 后端能稳定地枚举设备、设置 DPI、控制进纸器、拿到图像数据流,并且把这些动作塞进一个可维护的工程结构里。适合谁?做 OA、档案管理、医疗影像归档、票据识别这类系统的后端开发者,尤其是那些被“浏览器调不了扫描仪”逼到墙角、最后决定在本地起一个 Java 服务来兜底的人。

2. 拆开看结构:JNI 桥接层与 jtwain 的调用链

2.1 为什么是 JNI + TWAIN,而不是纯 Java 方案

先把这个选型逻辑说透,不然后面配环境全是玄学。TWAIN 的本质是一套 Windows 上的消息驱动接口,扫描仪厂商提供的是一个.ds文件(Data Source),你的程序通过DSM_Entry这个入口跟它对话。整个过程涉及窗口句柄、回调消息、内存锁,这些东西 Java 的 GC 和线程模型根本管不了。所以任何“纯 Java 扫描”的方案,底层一定藏着 JNI,只是有的库帮你封好了,有的要你自己写。

jtwain 这类库的价值在于:它把 TWAIN 的DSM_Entry调用、状态机流转、图像内存拷贝都封在 C 层,Java 侧只看到TwainSource、TwainIdentity这些对象。但封装不等于透明,一旦设备行为诡异,你还是得回到 JNI 那一层去排查。常见做法是:先用 jtwain 跑通基本扫描,遇到特定型号不兼容时,再针对性地改 C 层源码重新编译。

2.2 工程目录与关键文件

拿到源码包后,先别急着编译,把目录结构看清楚。一个典型的 Java 扫描仪工程大致长这样:

scanner-jni/ ├── native/ │ ├── twain_jni.c # JNI 入口,注册 native 方法 │ ├── twain_dsm.c # DSM_Entry 封装,状态机 │ └── twain_ds.c # 数据源操作,图像内存管理 ├── src/main/java/ │ └── com/example/scanner/ │ ├── TwainScanner.java # Java 侧主入口 │ ├── ScanConfig.java # DPI、色彩模式、纸张来源 │ └── ImageHandler.java # 回调,处理图像字节流 ├── lib/ │ └── jtwain.jar # 桥接库(若使用现成库) └── build.gradle

native/下是 C 代码,编译产物是一个.dll(Windows)或.so(Linux)。Java 侧通过System.loadLibrary("twain_jni")加载。这里有个细节:库名不带lib前缀和扩展名,JVM 会自己补。如果你在 Linux 上编译出来叫libtwain_jni.so,加载时写twain_jni就行。

2.3 核心调用链:从 Java 方法到扫描仪动作

以“枚举设备”为例,Java 侧调用TwainScanner.listSources(),实际发生的事是:

// Java 侧声明 public class TwainScanner { // 加载 native 库,只执行一次 static { System.loadLibrary("twain_jni"); } // native 方法声明,对应 C 层的 Java_com_example_scanner_TwainScanner_listSources public native String[] listSources(); public native boolean openSource(String sourceName); public native byte[] acquireImage(int dpi, int colorMode); }

对应的 C 层实现(简化):

#include <jni.h> #include "twain.h" JNIEXPORT jobjectArray JNICALL Java_com_example_scanner_TwainScanner_listSources(JNIEnv *env, jobject obj) { // 1. 打开 DSM,加载 TWAIN 管理器 if (DSM_Entry(&appId, NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &hwnd) != TWRC_SUCCESS) { return NULL; // 打开失败,返回空 } // 2. 枚举数据源,填充 sourceList // ... 省略具体循环,核心是 MSG_GETFIRST / MSG_GETNEXT // 3. 把 C 的字符串数组转成 Java 的 String[] jclass strClass = (*env)->FindClass(env, "java/lang/String"); jobjectArray result = (*env)->NewObjectArray(env, count, strClass, NULL); // ... 逐个 SetObjectArrayElement return result; }

逻辑说明:DSM_Entry是 TWAIN 的唯一入口,所有操作都通过它发消息。DG_CONTROL/DAT_PARENT/MSG_OPENDSM这组参数的意思是“打开数据源管理器”,必须最先调用,否则后面全失败。参数hwnd是窗口句柄,在无界面服务里通常传NULL或一个隐藏窗口的句柄,这一点后面避坑章节会细说。

参数说明:dpi控制分辨率,常见值 200/300/600,票据类 200 够用,档案类建议 300;colorMode一般 0 黑白、1 灰度、2 彩色,对应 TWAIN 的ICAP_PIXELTYPE。

3. 把环境跑起来:编译、加载与第一次扫描

3.1 Windows 下的编译与依赖

Windows 是 TWAIN 的主场,但编译 JNI 有点折腾。你需要:JDK(带jni.h和jni_md.h)、一个 C 编译器(MSVC 或 MinGW)、TWAIN 开发头文件(twain.h,从 TWAIN 工作组官网拿,这里不贴外链)。

用 MinGW 编译的命令大致是:

gcc -shared -o twain_jni.dll \ -I"%JAVA_HOME%\include" \ -I"%JAVA_HOME%\include\win32" \ -I"./native/include" \ native/twain_jni.c native/twain_dsm.c native/twain_ds.c \ -luser32 -lgdi32

-I指定头文件路径,%JAVA_HOME%是 JDK 安装目录。-luser32 -lgdi32链接 Windows 的窗口和图形库,TWAIN 的消息循环依赖它们。编译出来的twain_jni.dll放到 Java 的java.library.path能搜到的目录,或者启动时用-Djava.library.path=./lib指定。

3.2 Java 侧加载与设备枚举

编译通过后,写一个最小测试类:

public class ScanTest { public static void main(String[] args) { TwainScanner scanner = new TwainScanner(); String[] sources = scanner.listSources(); if (sources == null || sources.length == 0) { System.out.println("没找到扫描仪,检查驱动和 TWAIN 管理器"); return; } for (String s : sources) { System.out.println("发现设备: " + s); } // 打开第一个设备 if (scanner.openSource(sources[0])) { byte[] img = scanner.acquireImage(300, 2); // 300dpi 彩色 System.out.println("拿到图像字节数: " + (img == null ? 0 : img.length)); } } }

运行前确认java.library.path包含 dll 所在目录。如果listSources返回空,先别怀疑代码,九成是驱动或 TWAIN 管理器的问题——Windows 自带的“传真和扫描”能识别设备,不代表 TWAIN 数据源注册正常。

3.3 图像数据的落地:从字节流到文件

acquireImage返回的是原始字节,通常是 BMP 或未压缩的 DIB 格式。直接存成.bmp最简单:

import java.nio.file.*; byte[] img = scanner.acquireImage(300, 2); if (img != null) { // 假设 native 层返回的是完整 BMP 文件头+数据 Files.write(Paths.get("scan_output.bmp"), img); System.out.println("已保存 scan_output.bmp"); }

如果 native 层返回的是裸像素数据(没有文件头),你就得自己补 BMP 头,或者用ImageIO配合BufferedImage转成 PNG/JPG。这里的关键参数是图像宽度、高度、位深,这些信息要么在 native 层一起返回,要么通过额外的 native 方法获取。常见做法是让acquireImage返回一个包含元信息的对象,而不是裸byte[]。

提示:第一次跑通时,先用 200dpi 黑白模式,数据量小,出问题容易定位。彩色 600dpi 一张 A4 就是几十兆,内存和传输都容易翻车。

4. 避坑与排查:那些让扫描仪“罢工”的细节

4.1 现象:枚举不到设备,但系统里能看到扫描仪

原因:TWAIN 数据源没注册,或者 32/64 位不匹配。Windows 上 TWAIN 管理器分 32 位和 64 位两套,你的 JVM 是 64 位,但扫描仪驱动只装了 32 位数据源,两边对不上。

解决:确认 JDK 位数,然后装对应位数的扫描仪驱动。用Twain_32.dll还是TWAINDSM.dll取决于系统,可以在C:\Windows下看存在哪个。如果只有 32 位数据源,就换 32 位 JDK 跑,别硬扛。

4.2 现象:调用acquireImage后程序卡死,无返回

原因:TWAIN 的状态机没走完,或者消息循环被阻塞。TWAIN 的很多操作依赖 Windows 消息泵,如果你在 Java 的主线程里直接调 native 方法,而 native 层又在等一个永远不会来的消息,就死锁了。

解决:把扫描操作放到独立线程,并且确保 native 层有一个隐藏窗口在跑消息循环。常见做法是在 C 层创建一个HWND_MESSAGE类型的消息窗口,专门用来收 TWAIN 的回调。Java 侧用ExecutorService提交扫描任务,别在主线程干等。

4.3 现象:扫描出来的图像全黑或全白

原因:像素位深和色彩模式对不上。比如设备返回的是 24 位彩色,你按 1 位黑白去解析,数据就全乱了。或者 DPI 设得太高,设备实际不支持,返回了空数据。

解决:先查设备支持的ICAP_PIXELTYPE和ICAP_XRESOLUTION能力列表。TWAIN 提供MSG_GET来查询能力,别硬编码。一个稳妥的做法是:打开设备后先GET一遍能力,把支持的值打印出来,再根据实际支持的范围去设。

4.4 现象:连续扫描多张时,第二张开始报错

原因:TWAIN 会话没正确关闭,或者内存没释放。每扫完一张,必须发MSG_CLOSEDS关闭数据源,再重新MSG_OPENDS打开,否则状态机停在错误状态。

解决:把“打开-扫描-关闭”封装成一个完整的事务,每次扫描都走一遍。如果设备支持自动进纸器(ADF),用MSG_ENABLEDS配合CAP_FEEDERENABLED开启连续进纸,然后在回调里循环取图,而不是反复开关数据源。

4.5 现象:Linux 上编译通过,运行时报UnsatisfiedLinkError

原因:.so文件的依赖没装全,或者库名不对。Linux 下 JNI 库的命名规则和 Windows 不同,System.loadLibrary("twain_jni")找的是libtwain_jni.so。

解决:用ldd libtwain_jni.so看依赖有没有缺失。另外 Linux 上 TWAIN 支持很弱,多数场景应该换 SANE,通过sane-backends提供的 API 走另一套 JNI 封装。这份资源主要面向 Windows,Linux 下只能算“能编译”,实际设备兼容性得自己试。

5. 进阶:把扫描做成可配置的服务化接口

5.1 用配置文件驱动扫描参数

硬编码 DPI 和色彩模式在演示阶段没问题,上了生产就是灾难。我一般会抽一个ScanConfig类,从 YAML 或 properties 读参数:

public class ScanConfig { private int dpi = 300; private int colorMode = 2; // 0黑白 1灰度 2彩色 private boolean useFeeder = false; private String outputFormat = "png"; // 从 properties 加载 public static ScanConfig load(Path configFile) throws IOException { Properties props = new Properties(); try (InputStream in = Files.newInputStream(configFile)) { props.load(in); } ScanConfig cfg = new ScanConfig(); cfg.dpi = Integer.parseInt(props.getProperty("scan.dpi", "300")); cfg.colorMode = Integer.parseInt(props.getProperty("scan.colorMode", "2")); cfg.useFeeder = Boolean.parseBoolean(props.getProperty("scan.useFeeder", "false")); cfg.outputFormat = props.getProperty("scan.outputFormat", "png"); return cfg; } // getter/setter 省略 }

逻辑说明:Properties的getProperty第二个参数是默认值,这样配置文件缺项时不会崩。参数含义:scan.dpi建议 200-600,超过 600 多数办公扫描仪是插值出来的,没意义;scan.useFeeder只在有 ADF 的设备上开,平板扫描仪开了会报错。

5.2 封装成 HTTP 接口供前端调用

浏览器不能直接调 TWAIN,但可以调你本地起的 Java 服务。用com.sun.net.httpserver.HttpServer(JDK 自带,不用引 Spring)就能搭一个:

import com.sun.net.httpserver.*; import java.net.InetSocketAddress; public class ScanServer { public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(18080), 0); server.createContext("/scan", exchange -> { // 只接受 POST if (!"POST".equals(exchange.getRequestMethod())) { exchange.sendResponseHeaders(405, -1); return; } TwainScanner scanner = new TwainScanner(); String[] sources = scanner.listSources(); byte[] result = null; if (sources != null && sources.length > 0 && scanner.openSource(sources[0])) { result = scanner.acquireImage(300, 2); } if (result == null) { String msg = "scan failed"; exchange.sendResponseHeaders(500, msg.length()); exchange.getResponseBody().write(msg.getBytes()); } else { exchange.getResponseHeaders().set("Content-Type", "image/bmp"); exchange.sendResponseHeaders(200, result.length); exchange.getResponseBody().write(result); } exchange.close(); }); server.start(); System.out.println("扫描服务已启动,端口 18080"); } }

逻辑说明:createContext("/scan", handler)把/scan路径绑到处理逻辑。sendResponseHeaders第一个参数是状态码,第二个是响应体长度,-1表示无响应体。图像直接以image/bmp返回,前端拿到后可以转成 base64 显示或上传。注意这个服务只能跑在用户本机,因为扫描仪是本地设备,别想着部署到服务器上。

参数说明:端口选 18080 是避开常见冲突,实际用的时候检查一下有没有被占。acquireImage的 DPI 和色彩模式建议从请求参数里读,别写死。

5.3 验证扫描质量的三个硬指标

跑通不等于能用,我一般会盯三个指标:一是图像清晰度,300dpi 下 A4 文字边缘不能发虚,发虚多半是设备实际没跑到 300,用ICAP_XRESOLUTION的GETCURRENT确认;二是色彩还原,彩色模式下扫一张标准色卡,看偏色程度,偏得厉害就检查ICAP_BITDEPTH是不是被压到了 24 位以下;三是连续扫描的稳定性,ADF 连扫 50 张,看有没有卡纸、丢帧、内存泄漏。内存泄漏在 JNI 里很常见,每次acquireImage返回的byte[]如果 native 层没释放对应的 C 缓冲区,跑几百张就 OOM。

从那以后我每次接扫描仪项目,都强制先跑一遍“枚举-单张-连扫-释放”的完整链路,确认 native 层的内存引用计数没问题,再往上叠业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

QSPI PSRAM稳定运行实战:刷新调度、时序余量与控制器重构

QSPI PSRAM稳定运行实战:刷新调度、时序余量与控制器重构

“&#xff08;3&#xff09;”这个后缀说明它是系列里的第三篇。前两篇我们完成了APS6404L-3SQR这颗QSPI PSRAM的基本初始化、寄存器读写和裸读写验证&#xff0c;当时以为只要ID能读、数据能写就算打通了。真正开始跑连续数据流之后才发现&#xff0c;距离“能用”还有一大截…

2026/10/20 7:00:49 阅读更多 →
SLIME强化学习框架:从工程效率到实验管理的全面解析

SLIME强化学习框架:从工程效率到实验管理的全面解析

直接说结论&#xff1a;如果在强化学习项目里被“训练跑得慢、结果复现难、代码越写越乱”这三座大山压得喘不过气&#xff0c;那么SLIME这类框架就是拿来解决问题的。这几个月我一直在用SLIME&#xff08;某开源强化学习框架的代号&#xff09;跑各种连续控制实验&#xff0c;…

2026/10/20 7:00:49 阅读更多 →
同一个大模型,为什么换一套接口就能更会做事?

同一个大模型,为什么换一套接口就能更会做事?

同一个大模型&#xff0c;为什么换一套接口就能更会做事&#xff1f;你大概率遇到过这种怪事&#xff1a;同一个模型&#xff0c;同一个问题&#xff0c;在网页对话框里问它&#xff0c;回答得四平八稳、滴水不漏&#xff0c;但也没什么用&#xff1b;换到某个工具或者应用里再…

2026/10/20 7:00:49 阅读更多 →

最新新闻

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

简介&#xff1a;这份资源面向希望系统掌握Python数据分析与可视化的学习者&#xff0c;覆盖从编程基础到实战项目的完整技能链路&#xff0c;适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件&#xff0c;约31.27MB&#xff0c;以67个ipynb实验代码、11个…

2026/10/20 7:58:20 阅读更多 →
QClaw实测:基于大模型的微信数字分身从入门到避坑

QClaw实测:基于大模型的微信数字分身从入门到避坑

1. 项目概述与产品定位拆解1.1 先说清楚&#xff1a;QClaw到底是个什么东西大概几个月前我就留意到了QClaw这个项目&#xff0c;那会儿还是内测灰度阶段&#xff0c;圈子里已经有人开始传“微信里的AI分身”这个概念。这几天全面公测一开&#xff0c;群里热闹得不行。我用了一周…

2026/10/20 7:58:20 阅读更多 →
AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

前阵子一个做零售的朋友跟我吐槽&#xff1a;他们给客服智能体开了退单权限&#xff0c;结果一个晚上它因为一位客户反复输入“给我退钱”&#xff0c;把同一个小订单退了17次&#xff0c;财务第二天早上脸都绿了。这事本身不大&#xff0c;但它特别能说明问题——当智能体从一…

2026/10/20 7:58:20 阅读更多 →
自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化翻车这件事&#xff0c;做AI落地的团队迟早都会遇到一次。麦睿菱是我们内部一个AI自动化改造项目的代号&#xff0c;前后推进了三个多月&#xff0c;结果在试运行阶段就栽了跟头。今天想把它完整复盘一下&#xff0c;重点不是讲成功经验&#xff0c;而是聊聊自动化失败之…

2026/10/20 7:58:20 阅读更多 →
Agent模型选型不再难:运行时路由规则设计与实现

Agent模型选型不再难:运行时路由规则设计与实现

1. 为什么模型选型这件事&#xff0c;最后都变成了路由问题做过 Agent 项目的人大概都有过这么一个阶段&#xff1a;一开始兴致勃勃地对比各家模型&#xff0c;跑分、看榜单、测延迟、算成本&#xff0c;折腾了两周&#xff0c;最后在代码里写下了一个硬编码的模型名字。然后项…

2026/10/20 7:58:20 阅读更多 →
OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

这一篇原本是写给组里新同学的一份内部分享稿&#xff0c;后来想想干脆整理成公开文章。原因是带了好几次新人&#xff0c;发现大家第一次接触OpenCV时&#xff0c;最痛苦的根本不是算法&#xff0c;也不是深度学习模型&#xff0c;而是连“图片长什么样”“颜色为什么是反的”…

2026/10/20 7:57:19 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →