新闻详情

新闻详情

首页 / 资讯中心 / 详情

Qt跨平台开发实战:信号槽、多线程与部署避坑指南

发布时间:2026/10/2 1:05:47来源:尧图网络
Qt跨平台开发实战:信号槽、多线程与部署避坑指南
Qt 这个框架我前前后后用了快十年从 QWidget 到 QML从 Windows 到 Linux 再折腾到 Android说它是“跨平台开发利器”一点都不夸张。很多刚入门的朋友问我选什么技术栈做桌面软件我基本上都会推荐先看看 C 配合 Qt 这条路。原因很简单性能有保障生态成熟社区里你能踩到的坑基本上前面都有人替你趟过了。这篇东西我不会写成教科书就当作是跟你聊聊我个人在项目里积累下来的经验和思路。从环境搭建、信号槽理解、多线程避坑、绘图优化、国际化发布到最头疼的串口和部署问题全部揉碎了讲希望能帮你少走点弯路。1. 为什么跨平台开发首选 Qt而不是其他框架1.1 从一次真实的项目经历说起几年前我接手过一个工业控制软件要求同一套代码跑在 Windows 7 的老工控机和 Ubuntu 18.04 的现场服务器上还要支持后期迁移到 ARM 嵌入式平台。当时团队里有人提议用 Java有人拿 Electron 做原型。Java 的桌面端体验大家心里都有数Electron 倒是开发快可内存占用和启动速度在工控场景里根本交不了差。最后我拍板用 Qt Widgets 写C 编译成原生程序跨平台问题主要靠条件编译和 Qt 的抽象层解决。整个项目大概 20 万行代码现场跑了两年几乎没有因为平台差异出过毛病。这个经历想说明什么Qt 的跨平台不是写一遍然后在所有系统上刚好能跑而已而是从事件循环、文件系统、网络、线程到图形渲染每一层都帮你屏蔽掉了操作系统的差异。你用 QFile 读写文件在 Windows 和 Linux 上不需要关心路径分隔符是反斜杠还是正斜杠Qt 会负责处理。你用 QNetworkAccessManager 发 HTTP 请求底层是 WinHTTP 还是 OpenSSLQt 帮你搞定。这种抽象程度在桌面开发框架里确实难找对手。1.2 和其他技术路线的直接对比很多人在 Qt、GTK、Electron、wxWidgets 之间纠结我用一张表把关键差异列出来。维度Qt (C)GTK (C)Electron (JS)wxWidgets (C)渲染方式自绘引擎各平台观感一致调用系统原生控件Chromium 渲染调用系统原生控件性能高编译型语言加高效绘图中高低内存大户中高开发效率中高信号槽机制很好用中极高中UI 灵活性自由绘制适合工控和定制界面一般极高适合酷炫 Web 风一般受限系统控件跨平台范围Windows、Linux、macOS、Android、iOS、嵌入式覆盖面广但移动端弱桌面三平台为主桌面为主学习曲线较陡但理念清晰后一通百通较陡平缓中等Electron 的 UI 表现力确实强但一个记事本程序装完几百 MB 内存在讲究资源利用率的场景直接出局。GTK 的 C API 写起来繁琐而且在不同发行版上版本分裂严重我踩过 GTK3 和 GTK4 接口不兼容的坑真心觉得 Qt 的 API 稳定太多了。如果目标是做严肃的桌面产品Qt 加 C 依然是综合成本最低的路线。1.3 什么样的人适合选择 Qt我的建议比较直接如果你的定位是长期做客户端开发、工控上位机、音视频工具、仿真软件这类追求性能的本机应用直接学 Qt。如果只是临时需要一个小工具界面要求不高那用 Python 加 PyQt 也可以原理完全相同只是部署时多一个 Python 环境依赖下文我会专门讲部署问题。2. Qt 环境搭建与第一个跨平台程序2.1 下载安装那些事先解决一个高频问题Qt 到底去哪里下载、怎么装。官方安装器现在强制要求注册账号而且在线安装器在国内尤其是老网络环境下容易失败。我的习惯是直接使用镜像站下载离线安装包速度稳定很多。搜索“qt 离线安装包 5.14”或“qt 5.15.2 下载安装”就能找到许多开源镜像里面的文件名带有操作系统标识比如 qt-opensource-windows-x86-5.14.2.exe 这样的就是 Windows 离线包。安装时有几个组件选择要点。以 Windows 为例编译器选 MinGW 或 MSVC 之前要先想清楚。MinGW 是 GCC 的 Windows 移植版走开源工具链配置相对简单适合新手上路MSVC 是微软的编译器与 Windows 平台 API 结合更紧密如果你要调用 Windows 特有的接口或者某些第三方库只提供 MSVC 编译好的库文件就必须用 MSVC。两个二选一都会让你少掉很多麻烦别同时在同一个项目里混用。提示安装 Qt 时不要把路径放在带空格的目录我见过太多人装在 C:\Program Files\Qt导致 CMake 和 qmake 在解析路径时出现莫名其妙的错误。用 D:\Qt 或 C:\Qt 这种简洁路径省心很多。2.2 环境变量与构建工具链装好 Qt 之后把 Qt 的 bin 目录例如 D:\Qt\5.15.2\mingw81_64\bin和编译器目录加入系统 PATH。这一步能让命令行下的 qmake、mingw32-make 等工具直接可用。如果后面要跑 CMake还需要把 CMake 也配到 PATH 里。实际项目中我更喜欢用 CMake 而不是 qmake 构建尤其在写多模块项目时CMake 的依赖管理、测试集成、跨平台配置都明显更顺手。一个最简 CMakeLists.txt 长这样cmake_minimum_required(VERSION 3.16) project(MyFirstQtApp) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Widgets) add_executable(MyFirstQtApp main.cpp widget.cpp widget.h) target_link_libraries(MyFirstQtApp PRIVATE Qt6::Widgets)AUTOMOC、AUTORCC、AUTOUIC 这三个开关一定要打开。AUTOMOC 会自动处理带 Q_OBJECT 宏的头文件并生成 moc 文件AUTORCC 负责编译 .qrc 资源文件AUTOUIC 处理 .ui 文件。很多初学者遇到“unknown type name Q_OBJECT”或者槽函数不响应十有八九是 AUTOMOC 没开或者是 .h 文件没写进 add_executable 的源文件列表里。2.3 写一个跨平台 Hello World环境搞定之后新建一个 main.cpp#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello from Qt on Windows / Linux / macOS); label.resize(320, 120); label.show(); return app.exec(); }这段代码在三种平台上几乎一字不改直接编译。Windows 上编译出来的窗口和 Linux 上窗口可能观感有所不同因为字体和主题渲染差异但代码层面是无缝的。这就是 Qt 的价值你只需要维护一套核心逻辑界面边缘的适配交给框架层处理。3. 信号槽机制Qt 的灵魂也是很多人绕不过去的坎3.1 信号槽到底在解决什么问题传统的 GUI 编程里控件之间的通信通常通过回调函数实现。回调的问题在于把“谁调用谁”写死了界面逻辑一旦复杂你就看到一堆函数指针互相纠缠。Qt 的信号槽机制把通信的双方解耦发送方只需要发信号不需要关心谁在接收接收方只需要连接信号也不需要知道信号来自哪里。这种设计非常适合复杂界面因为任意两个对象可以在运行时建立连接也可以随时断开组件之间完全不知道对方存在。生活化类比一下你订了一份报纸你和送报员之间不需要直接认识。你只要在邮局登记了地址报纸出版后自然会被送过来不想要了打个电话取消订阅就行。信号槽里的 Signal 相当于出版通知Slot 相当于你的收件行为connect 函数就是那笔登记。3.2 五种连接方式的辨析Qt 5 之后推荐使用新式语法类似下面这样connect(sender, SenderClass::valueChanged, receiver, ReceiverClass::handleValue);这种写法在编译期就能检查信号和槽是否存在参数类型不匹配直接编译报错比老式字符串写法安全得多。老式写法 connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(handleValue(int))) 只有在运行时才会报“No such slot”的错排查起来特别费劲。连接方式的第五个参数可以指定连接类型默认是 AutoConnection。它表示如果信号和槽在同一个线程直接连调用是同步的如果跨线程Qt 会自动采用 QueuedConnection把信号事件放入接收者线程的事件循环中调用变成异步。这个机制是 Qt 多线程编程的基础也是“在槽函数里修改 UI 竟然能正常工作”的底层原因。3.3 自定义信号与槽的实战写法我自己封装一个日志模块时经常这样写class Logger : public QObject { Q_OBJECT public: using QObject::QObject; signals: void logMessage(const QString level, const QString message); };在其他界面类中连接connect(m_logger, Logger::logMessage, this, MainWindow::showLog);这里注意带 Q_OBJECT 宏的类如果继承自 QObject头文件必须经过 moc 处理。在 CMake 里只要开启了 AUTOMOC并且头文件被加入了 target_sources就一切自动。如果你用 qmake记得把新加的带 Q_OBJECT 的头文件加到 HEADERS 变量里不然运行时报“Unknown signal”或者连接不上。槽函数可以像普通成员函数一样声明也可以直接连接到一个 lambda 表达式。我在处理简单的按钮点击时经常写connect(ui-refreshButton, QPushButton::clicked, this, [](){ reloadData(); ui-statusLabel-setText(tr(数据已刷新)); });lambda 写起来简洁但有个注意点如果 lambda 捕获了 this而接收者在事件循环中已经销毁那就可能变成悬垂指针。解决办法是第五个参数传 Qt::UniqueConnection 不解决这个问题应该配套使用 QPointer 或 QObject::deleteLater确保及时断开连接。推荐在析构函数里调用 disconnect 或者直接把接收者指定为 thisQt 会在对象销毁时自动清理所有相关连接避免大部分悬垂风险。4. 多线程与耗时任务从界面卡死到流畅通顺4.1 界面卡死的根源UI 线程更准确地说主线程是 Qt 事件循环所在的地方。所有控件的绘制、鼠标键盘事件处理、定时器触发都在这个线程里做。如果在主线程直接执行耗时的操作例如从串口读大量数据、执行复杂排序、等待网络响应那这段时间里事件循环被阻塞界面就“死了”。你拖动窗口没反应按钮点击后不见反馈其实就是事件循环被卡住。我在开发一个数据采集软件初期直接在按钮槽里写了一个循环去读串口数据结果现场一启动采集按钮整个程序瞬间无响应客户那边直接打电话过来问是不是死机了。后来才意识到这类操作必须交给后台线程。4.2 推荐的线程方案Qt 官方推荐的方案是 QThread 配合 moveToThread推荐度较高的是 QThreadPool 加 QRunnable以及 Qt 5.10 之后的 QtConcurrent。我这里把常用方案对比一下。线程方案适用场景优点坑点继承 QThread 并重写 run简单后台任务易理解代码直观注意不要在 QThread 对象上直接调用 start 后在 run 里操作信号槽容易误用moveToThread复杂后台对象有信号槽交互对象生命周期可控信号槽连接清晰从主线程直接调用该对象的成员函数仍是主线程执行需要靠信号触发QtConcurrent::run一次性耗时操作代码简洁返回 QFuture不能处理需要在同一线程持续运行的场景moveToThread 的原理是把一个 QObject 对象“迁移”到新线程之后该对象的槽函数如果通过信号触发就会在对象所在线程执行。举个例子class Worker : public QObject { Q_OBJECT public slots: void doHeavyWork() { // 这里在主线程外面执行 QThread::msleep(3000); emit workDone(); } signals: void workDone(); }; // 使用代码 QThread *workerThread new QThread(this); Worker *worker new Worker; worker-moveToThread(workerThread); connect(workerThread, QThread::finished, worker, QObject::deleteLater); connect(ui-startButton, QPushButton::clicked, worker, Worker::doHeavyWork); connect(worker, Worker::workDone, this, MainWindow::handleWorkDone); workerThread-start();这个模式的关键是要用信号去触发 worker 对象的槽函数而不是直接调用 worker-doHeavyWork()。直接调用的话doHeavyWork 仍然在主线程里执行又把界面卡死了。这个坑我见到过不止一次几乎每个初学 Qt 多线程的人都掉进去过。4.3 线程安全的 UI 更新后台线程跑完了任务怎么安全地更新界面最简单可靠的方式是用信号槽跨线程通信槽函数在接收者线程中执行自然可以操作界面。上文例子中 workDone 信号就是这样。还有一种方式是用 QMetaObject::invokeMethodQMetaObject::invokeMethod(this, updateProgress, Qt::QueuedConnection, Q_ARG(int, percent));这个调用会把更新操作投递到目标对象所在线程的事件循环里虽然不是通过信号槽但机制一样都是跨线程安全的。在真正常用的场景里我会封装一个线程安全的日志类后台线程通过信号把日志文本发送给 UI 线程的槽函数显示这样日志列表始终能实时刷新又不会崩溃。注意绝不能在后台线程里直接调用 QWidget 的成员函数比如 setText、show、repaint。UI 控件线程亲和性检查非常严格在非 GUI 线程操作会导致崩溃或未定义行为。出了问题还会随机出现在不同机器上排查起来特别头疼。5. 绘图与自定义控件界面的面子工程5.1 绘制原理简述Qt 的绘图系统核心是 QPainter它可以在 QWidget、QImage、QPixmap 上绘制各种图形。绘图操作通常发生在 paintEvent 中系统认为需要重绘时就会调用这个事件。你需要做的就是把当前状态下应该画成什么样在 paintEvent 里全部画出来。我开发的一个仪器控制软件里需要实时绘制温度曲线和电压曲线。最开始直接在 QWidget 的 paintEvent 里画所有数据点点数一多画面就闪烁且卡顿。后来改成把绘制的图形渲染到 QPixmap 上只在数据变化时局部更新贴图再通过 update() 触发重绘。这样渲染效率和流畅度都上来了。这个技巧本质上就是 double buffering 的思想Qt 的很多高级控件内部也是这么做的。5.2 自定义进度条的两种实现你搜“qt 自定义进度条”时能看到各种方案这里我拆解两个常用思路。方案一是继承 QWidget重写 paintEvent。我在某个需求中要做一个圆形进度指示类似汽车仪表盘。class CircleProgress : public QWidget { Q_OBJECT public: using QWidget::QWidget; void setPercent(qreal percent) { m_percent qBound(qreal(0), percent, qreal(1)); update(); // 触发重绘 } protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QRectF rect QRectF(0, 0, width(), height()).adjusted(8, 8, -8, -8); QPen pen; pen.setWidth(8); pen.setColor(Qt::lightGray); painter.setPen(pen); painter.drawEllipse(rect); pen.setColor(QColor(#3b82f6)); painter.setPen(pen); painter.drawArc(rect, 90 * 16, int(-360 * m_percent * 16)); } private: qreal m_percent 0; };注意 drawArc 的角度单位是 1/16 度这是 Qt 初学者几乎必踩的坑。角度从三点钟方向顺时针计算所以起点用 90 * 16 表示从顶部开始。方案二是使用 QStyleOptionProgressBar 让自定义控件支持系统样式表。这种方式不用自己画具体内容而是借用 QStyle 的基础行为再对细节做微调兼容 QSS 的效果更好。项目中如果对观感一致性要求高我更偏好方案一因为它完全掌控绘制过程不受系统主题影响。5.3 绘图效率对比与优化意见网上有个热门问题“qt绘图效率比较”问 QPainter 画在 QWidget 上和画在 QOpenGLWidget 上哪个快。结论很明确如果只是绘制 2D 矢量图形比如几百个矩形、线段、文字QPainter 加 QWidget 在多数普通场景下已经足够但当数据量达到数万甚至上百万个图形元素时QPainter 的 CPU 光栅化会成为瓶颈就该换 QOpenGLWidget 用 GPU 加速。实际优化顺序我通常是这样的优先检查是否真的是绘制开销还是数据准备开销。很多时候卡顿不是因为 QPainter 慢而是在 paintEvent 里做了文件读取或复杂计算。其次把所有不变的图形缓存到 QPixmap只有变化的部分重绘。最后多数据点时考虑用 QPainterPath 合并路径一次调用绘制比循环一万次 drawLine 高效得多。6. 国际化、打包发布与常见问题排查6.1 界面多语言切换的完整流程Qt 的国际化方案很成熟核心是 tr()、QTranslator、Qt Linguist。你用 tr(Hello) 包裹界面文本Qt 会根据当前安装的翻译文件自动替换。步骤拆开讲源码中所有面向用户的字符串写成英文或中文并用 tr() 包裹。在 .pro 文件中加入 TRANSLATIONS myapp_zh_CN.ts更新 TS 文件。用 lupdate 扫描源码生成 TS 文件。在 Qt Creator 里执行“工具 - 外部 - Qt Linguist - 更新翻译”。用 Qt Linguist 打开 TS 文件逐条翻译。用 lrelease 把 TS 编译成 QM 文件。程序运行时根据当前系统语言加载相应的 QM 文件。动态切换界面语言时最直接有效的办法是对主窗口等界面类调用 QEvent::LanguageChange 事件通知界面刷新然后在事件处理函数里把界面文本重新用 tr() 赋值一遍。还有个土办法是直接重建所有窗口对象的 retranslateUi 接口这个在 Qt Designer 生成的界面中都会自动存在。6.2 程序发布时的依赖处理Qt 程序编译出来不是绿色可执行文件它依赖 Qt 的 DLL 或 so 文件。Windows 上要用 windeployqt 工具自动收集依赖。windeployqt --release --no-translations MyApp.exe这个命令会把需要的 Qt6Core.dll、Qt6Widgets.dll、平台插件 platforms/qwindows.dll 等拷贝到可执行文件旁。发布前最后一步是检查 release 目录里多不多无用的文件、缺不缺关键的插件特别是 imageformats 里的图片格式插件和 platforms 里的窗口插件。少了 platforms 文件夹最常见的报错是“This application failed to start because no Qt platform plugin could be initialized”。这个问题我在客户机器上遇到过 3 次次次都是漏拷贝 platform 插件或者环境变量指向了开发模式下其他 Qt 版本。Linux 部署一般用 linuxdeployqt或者直接用 AppImage 方式打包。AppImage 的优势是打包完单个文件扔到任何主流 Linux 发行版上都能跑起来不依赖系统里有没有装 Qt。嵌入式 ARM 环境中交叉编译则要提前配置好 sysroot 和工具链安装时选择对应的编译目标。这里可以搜到“ubuntu-20.04 安装 qt 交叉编译环境”之类的教程核心点就是截取目标系统的根文件系统交给 Qt 的交叉 mkspec。6.3 高频报错与解决方案速查下面这张表是我这么多年帮助同事和网友排查时整理出的高频问题清单。报错信息原因解决方案unknown module(s) in QT: serialport缺少串口模块组件安装时勾选Qt Serial Port模块或用离线包补装error: Microsoft Visual C 14.0 or greater is requiredPython 环境的 VC 编译器版本过低安装最新的 Microsoft Visual C Redistributable或在 Qt 中切换 MSVC 编译套件This application failed to start because no Qt platform plugin could be initialized缺少 platforms 插件或 Qt 路径不一致执行 windeployqt 并检查 platforms/qwindows.dll 是否就位no such slot 或 QObject::connect: No such signalmoc 文件未生成或函数签名不匹配开启 AUTOMOC检查信号槽参数类型是否完全一致undefined reference to vtable for类名Q_OBJECT 宏的文件没参与 moc 编译流程确认头文件已加入 HEADERS 或 target_sources 中MySQL driver not loadedQt 缺少数据库驱动插件在 Qt 安装中勾选对应数据库插件或手动编译 qsqlmysql 插件6.4 关于“qt 命令行编译”和“qt 调用外部库”的一点提示命令行编译 Qt 项目并不神秘。新建一个文件夹写完后通过 qmake 或 cmake 生成 Makefile然后用 nmake 或 mingw32-make 编译。这条路径能加深你对 C 构建过程的理解特别是链接阶段那些找不到符号的问题在 IDE 里被隐藏了不少在命令行下全都会暴露出来。调用外部库时例如你搜“qt 调用proj”“qt怎么调用halcon”核心步骤都是三步找到头文件路径、找到库文件路径、在链接时加上库名。Qt 的 .pro 文件里对应写法是 INCLUDEPATH 和 LIBSCMake 则用 target_include_directories 和 target_link_libraries。我记得第一次在 Qt 里调用 HALCON 时折腾了整整一个晚上才知道 HALCON 的库文件不仅需要加 h concat 前缀和 .lib 后缀还需要在运行时把 HALCON 的 bin 目录加入 PATH。这种库依赖问题就是这样多踩一次后面就顺畅了。7. 让 Qt 项目更稳更专业的几个观念写 Qt 项目容易写好 Qt 项目难。这里分享几个我长期坚持的实操习惯。第一严格遵循模型视图分离。耗时数据处理放后台线程界面只做展示和交互。我见过太多人把 SQL 查询直接写在按钮槽函数里数据量小时没问题数据量一大就卡死看代码时还特别难改。第二合理使用对象树管理内存。Qt 的父子对象机制非常方便new 一个子控件时传入父对象父对象析构时自动删除子控件。但小心别把栈上对象传给父对象否则会 double delete直接崩溃。开发后期排查崩溃基本都是这类问题。第三资源管理用 QPointer 监护。当你持有一个 QObject 指针而这个对象可能在别处被销毁用 QPointer 能自动置空防止悬垂指针。我有一段网络通讯模块就吃过这个亏吃了一次之后所有跨模块对象引用都改用 QPointer 了。第四善用 Qt 模块裁剪。Qt 的很多模块可以裁剪编译如果你做嵌入式项目只保留 Core、Gui、Widgets、Network 等实际用到的模块能大幅减小体积和依赖数。发布软件时体积从 300MB 优化到 80MB就是靠这个思路。每个人刚开始写 Qt 都会经历写界面、卡死、查信号槽、搜报错的循环。我刚接触 Qt 那会儿也曾在串口模块怎么都连不上、程序一启动就闪退的泥潭里挣扎过好几天。但 Qt 的好处是它是一个有规律、可分解的体系只要把信号槽、事件循环、线程亲和这几根主梁搭起来其他的部件自然就能挂靠上去。希望这些踩过坑沉淀下来的经验能让你的第一步稍微轻松一些。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AI率太高怎么办?2025年降AI率实用工具与改写方法论 2026/10/2 1:43:35

AI率太高怎么办?2025年降AI率实用工具与改写方法论

每次一到期末季,我的私信里就会出现同一个问题:“学长,AI率太高怎么办?”“老师要求AI率低于20%,我用AI写的都要改吐了。”说实话,2025年了还在纠结怎么“骗”过AI检测,这个出发点本身就跑偏了。…

阅读更多 →
软文发稿平台性能优化实战:从4.3秒到1.9秒的提速之路 2026/10/2 1:43:35

软文发稿平台性能优化实战:从4.3秒到1.9秒的提速之路

做一个软文自助发稿平台,最怕的不是内容不好,也不是渠道不够多,而是用户点开页面,先对着白屏等三秒。我接手软文匠自助发稿平台性能优化的时候,后台数据显示得很直白:页面平均加载时间4.3秒,首屏…

阅读更多 →
机器学习票房预测源码全解析:从数据清洗到模型训练 2026/10/2 1:43:35

机器学习票房预测源码全解析:从数据清洗到模型训练

简介:面向机器学习学习者与电影数据分析从业者的电影票房预测平台资源包。该平台整合历史票房、影片信息、市场趋势及观众评价等多源数据,提供数据清洗、特征工程、模型训练、预测分析与结果可视化的完整闭环,支持线性回归、随机森林、神经网…

阅读更多 →
EMC整改实战:传导发射超标原因与PCB地分割优化 2026/10/2 1:43:35

EMC整改实战:传导发射超标原因与PCB地分割优化

我无法根据当前输入生成符合要求的博文内容。原因如下:输入中仅提供了项目标题“6.2 EMC”,未提供任何实质性的【项目正文】、【关键词】或【摘要描述】;所附“基于标题及热词网络搜索的内容”部分为空(内容为空)&…

阅读更多 →
Umi-OCR:截图取字、批量图片识别、PDF 扫描件,3 个本地 OCR 任务一网打尽,不联网不花钱 2026/10/2 1:43:29

Umi-OCR:截图取字、批量图片识别、PDF 扫描件,3 个本地 OCR 任务一网打尽,不联网不花钱

Umi-OCR:截图取字、批量图片识别、PDF 扫描件,3 个本地 OCR 任务一网打尽,不联网不花钱 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除…

阅读更多 →
action-gh-release 版本演进全解读:从 v0.1 到 v3.0 的 GitHub Release 自动化实战 2026/10/2 1:43:28

action-gh-release 版本演进全解读:从 v0.1 到 v3.0 的 GitHub Release 自动化实战

开发者工具CI/CD 【免费下载链接】action-gh-release 📦 :octocat: GitHub Action for creating GitHub Releases 项目地址: https://gitcode.com/GitHub_Trending/ac/action-gh-release 点击查看 免费下载 action-gh-release 是一个在 Linux、Windows、…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉