☰
Qt调用大漠插件3.1233实现自动发消息:从COM绑定到队列落地
2026/9/26 18:24:20 网站建设 项目流程

简介:面向需要使用Qt完成自动化操作的C++开发者,这份源码演示了如何结合大漠插件3.1233实现自动发送消息、自动注册等功能,尤其适合微信批量消息处理、自动化测试、按键模拟辅助等场景。大漠插件3.1233免费且带中文手册,降低了非英语开发者的门槛。压缩包共68个文件,约21.97MB,含24个dll动态库(大漠插件及Qt运行依赖)、7个exe可执行程序、4个cpp和3个h源码文件,另有qm翻译文件、ui界面、chm帮助文档、bat脚本与7z压缩包等,运行与二次开发所需材料基本齐备。已有4870人学习/下载。包内给出了完整Qt工程,包含主窗口界面设计和dmobject等封装代码,可直接打开pro文件进行编译,配合自动发消息exe和对应dll即可快速验证效果;chm中文手册方便查阅大漠API,bat脚本辅助部署发布。整体目录结构清晰,从调用流程到界面交互均有示例,适合C++/Qt初学者理解插件封装思路,也适合中高级开发者提取模块用于自己的项目。

1. 为什么 Qt + 大漠插件 3.1233 做自动发消息值得自己写一套源码

凌晨三点还在手动给十几个窗口发消息,这种事我干过不止一次。后来用 Qt 做界面、调大漠插件 3.1233 的 COM 接口,把“找窗口、填内容、点发送”写成了一套源码,从此这类重复劳动再没占过我睡觉时间。大漠插件是 Windows 桌面自动化里很老牌的工具,免费版 3.1233 功能砍得狠,但找字、找图、键鼠模拟、窗口绑定这些核心能力都在,配合 Qt 做任务队列和日志面板,比按键精灵脚本更可控,也比纯 Qt 硬写 mouse_event 少踩一堆坐标换算的坑。这套方案适合有 C++/Qt 基础、要给指定窗口做定时或批量消息发送的人,本文就把从 COM 加载到队列落地的完整路径讲清楚。

2. Qt 进程里接入大漠插件 3.1233:COM 初始化与窗口绑定

2.1 大漠插件免费版 3.1233 的能力边界:先看清楚再动手

大漠插件 3.1233 是网上流传很广的经典免费版本,版本号本身就是一个重要的边界标记。3.1233 之后的付费版本开放了后台绑定、内存读写、进程保护、多开支持等高级功能,而免费版 3.1233 能稳定用的是前台自动化这一套:FindWindow 找窗口句柄、BindWindow 绑定窗口、MoveTo/LeftClick 模拟鼠标、KeyPress 模拟键盘、SetClipboard 写剪贴板、FindStr/Ocr 做文本识别、Capture 截图。这些能力组合起来恰好覆盖“自动发消息”的全部环节。

选这个版本而不是追新,有两个实际理由。一是 3.1233 的接口稳定,网上大量按键精灵脚本和大漠教程都以 3.x 接口为准,遇到函数签名问题容易查;二是免费版没有注册码过期、后台服务心跳这类干扰,放进 Qt 程序里就是一个纯粹的 COM 组件,启动快、行为可预期。它不能做的是后台绑定收费功能——比如窗口最小化时继续发消息,这个后面避坑章节会细说。

常见做法是把这个 3.1233 版本的 dm.dll 放在工程目录下的vendor/dm/里,用 regsvr32 注册后通过 ProgID 创建对象。很多 Qt 工程里会看到“大漠插件绑定窗口失败”的求助帖,十有八九不是代码问题,是位数不对或者注册方式错误,2.2 节先解决这块。

2.2 在 Qt 里加载大漠 COM 组件:pro 配置与最小初始化代码

大漠插件在 Windows 上以 COM/ActiveX 组件形式存在,Qt 里直接对应QAxObject。加载前提有三个:Qt 工程开启 axcontainer 模块、dm.dll 已注册、Qt 程序位数与大漠组件一致。大漠 3.1233 是 32 位组件,所以 Qt 程序也要编译成 32 位,这是最容易翻车的一步,我下面给了完整配置。

pro 文件里加一行:

QT += core gui axcontainer

大于 Qt 5.15 或 Qt 6 的工程还需要确认 MSVC/MinGW 工具链是 32 位模式。在 Qt Creator 里对应 Kit 选择 “Desktop Qt 5.15.2 MSVC2015 32bit”,不要选 64 位。

注册 dm.dll 用管理员身份打开命令提示符:

regsvr32 D:\project\vendor\dm\dm.dll

注册成功会弹提示框,没弹的话直接看 2.2 的失败分支。接下来在代码里创建组件:

#include <QAxObject> #include <QDebug> QAxObject* createDmInstance(QObject* parent) { QAxObject* dm = new QAxObject("dm.dmsoft", parent); if (dm->isNull()) { qWarning() << "大漠组件创建失败,请确认 dm.dll 已注册且程序位数为 32 位"; delete dm; return nullptr; } // 调用 Ver() 拿版本号,用于确认加载的是 3.1233 QVariant ver = dm->dynamicCall("Ver()"); qInfo() << "大漠插件版本:" << ver.toString(); return dm; }

这段代码的逻辑是先按 ProgIDdm.dmsoft创建 COM 对象,再调用Ver()做一次真实通信,防止出现“对象建出来了但接口不可用”的假象。dynamicCall是大漠接口在 Qt 里的标准调用方式,第一个参数是函数签名,后面跟参数列表。这里Ver()没有参数,返回的就是版本字符串。

注意,如果Ver()返回空串或调用直接抛异常,优先检查两件事:dm.dll 是否真的注册成功,以及 Qt Kit 的位数。常见做法是先单独写一个最小 console 工程只跑这段代码,确认通过后再往界面工程里迁移,能省掉后面一半的排查时间。

2.3 找到目标窗口并完成绑定:三种模式怎么选

自动发消息的第一步是拿到目标窗口的句柄。大漠的FindWindow接受窗口类名和标题两个参数,通常标题比类名可靠。以定时给某款聊天窗口发消息为例:

long findWindowHandle(QAxObject* dm, const QString& title) { // 第一个参数是窗口类名,传空串""表示不限制类名,只按标题查找 QVariant ret = dm->dynamicCall( "FindWindow(QString,QString)", QString(""), title); return ret.toInt(); // 返回窗口句柄,0 表示未找到 }

找到句柄后调用BindWindow绑定。3.1233 免费版的前台绑定,display、mouse、key 三个模式参数基本用 “normal”,mode 传 0 或空串:

bool bindWindow(QAxObject* dm, long hwnd) { QVariant ret = dm->dynamicCall( "BindWindow(int,QString,QString,QString,int)", (int)hwnd, QString("normal"), QString("normal"), QString("normal"), 0); int code = ret.toInt(); if (code != 1) { qWarning() << "绑定失败,返回码:" << code; return false; } return true; }

这里三个 normal 分别代表显示模式、鼠标模式、键盘模式:display 用 normal 意味着目标窗口不能最小化或遮挡,必须可见;mouse 用 normal 表示鼠标操作走真实光标,不虚拟注入;key 用 normal 表示按键走前台消息。这套组合的好处是兼容性最好,几乎所有 Windows 原生窗口都吃,缺点是发消息过程中你不能去碰别的窗口。

如果目标程序是游戏或自绘界面的 DirectUI 窗口,normal 鼠标模式可能失效,这时可以尝试把 mouse 参数换成windows模式,让大漠用 Windows 消息投递代替物理鼠标移动。但这种模式下部分程序会拒绝消息,我在第 5 章避坑里会专门说。绑定成功之后,大漠的所有坐标操作都基于窗口客户区,后续找输入框就不用再换算屏幕坐标了,这也是选大漠而不是直接用 Qt 的 QCursor::setPos 模拟鼠标点击事件的核心原因。

3. 自动发消息核心实现:定位输入框、填内容、点发送

3.1 输入框定位的三种方式:找字、找图、固定坐标

绑定窗口后,要定位“输入框”和“发送按钮”的位置。最省事的是固定坐标——目标窗口不变化时,用大漠自带工具取一次坐标写进配置文件就行;但窗口尺寸一变就失效,所以我主要用找字和找图两种动态方案。

找字是用FindStr系接口在窗口区域内搜索指定文本,比如找“发送”按钮。大漠的 FindStr 返回的是字符串索引和坐标,但在 Qt 的 dynamicCall 里传输出参数比较别扭,常见做法是改用FindStrEx——它把每个匹配点的坐标拼在一个返回串里,用 Qt 解析字符串就能拿到坐标:

struct Point { int x; int y; }; bool findTextInWindow(QAxObject* dm, const QString& text, int x1, int y1, int x2, int y2, QList<Point>& results) { // FindStrEx(x1,y1,x2,y2,字符串,颜色格式,相似度) 返回 "0,x,y|1,x,y" 形式的坐标串 QVariant ret = dm->dynamicCall( "FindStrEx(int,int,int,int,QString,QString,double)", x1, y1, x2, y2, text, QString("000000-000000"), 0.9); const QString raw = ret.toString(); if (raw.isEmpty()) { return false; } const QStringList matches = raw.split('|', Qt::SkipEmptyParts); for (const QString& m : matches) { QStringList parts = m.split(','); if (parts.size() >= 3) { Point p; p.x = parts[1].toInt(); p.y = parts[2].toInt(); results.append(p); } } return !results.isEmpty(); }

参数里"000000-000000"是颜色格式,表示匹配黑色文字、偏差范围 000000,按大漠的约定写成“前景色-偏色”。相似度 0.9 是经验值,3.1233 对低分辨率字库的识别不算稳,太高压不住误匹配,太低找不着,0.9 在中文界面上基本能用。

找图是更稳的替代方案:先用大漠的 Capture 截一张“发送”按钮的小图存成 bmp,再调用FindPic。做法类似,但 FindPic 同样有输出参数问题,更省心的做法是直接用大漠自带的图色工具生成查找代码片段,再手工改成 dynamicCall。固定坐标则最简单:目标窗口固定大小、固定布局时,直接把坐标写进一个 QSettings 配置文件里,代码里少走一次识别,速度最快。三种方式可以共存,先用固定坐标,失效时再回退到找字找图。

3.2 模拟鼠标点击与组合键输入:为什么用剪贴板而不是直接打宇

定位到输入框后,动作序列是:点击输入框获得焦点、写入文本、点击发送。写入文本这里有个关键选择:用大漠的KeyPressStr直接模拟按键输入,还是用剪贴板 + Ctrl+V 粘贴。

常见做法是剪贴板方案。原因很简单:KeyPressStr 在 3.1233 免费版对中文和特殊字符的支持很差,它本质上是把字符串映射到按键码,遇到中文就丢字或乱码;而剪贴板方案不经过按键翻译,Unicode 文本原样送进去,发什么就是什么。代价是需要多几次延迟调用,但稳定性值得。

void clickPoint(QAxObject* dm, int x, int y, int delayMs = 50) { dm->dynamicCall("MoveTo(int,int)", x, y); dm->dynamicCall("LeftClick()"); QThread::msleep(delayMs); } bool sendTextByClipboard(QAxObject* dm, const QString& text, int inputX, int inputY, int sendBtnX, int sendBtnY) { // 1. 点击输入框,让焦点落在正确的位置 clickPoint(dm, inputX, inputY, 80); // 2. 写入剪贴板并粘贴 dm->dynamicCall("SetClipboard(QString)", text); QThread::msleep(50); // 等剪贴板就绪,太快直接 Ctrl+V 会粘到旧内容 dm->dynamicCall("KeyPress(int,int)", 17, 86); // 17=Ctrl, 86=V // 3. 等粘贴完成,再点发送按钮 QThread::msleep(150); clickPoint(dm, sendBtnX, sendBtnY, 100); // 4. 清空剪贴板,避免下次误粘贴 dm->dynamicCall("SetClipboard(QString)", QString("")); return true; }

KeyPress(17, 86)的参数含义是同时按下 Ctrl 和 V,这是大漠组合键的写法——第一个参数是主键,第二个是修饰键。注意最后一步清空剪贴板很重要,很多自动发消息脚本跑到后面突然发出上一轮残留内容,都是栽在没做这一步。

3.3 一个可直接抄的自动发送函数:完整串联

把前面几段合并成一个带返回值状态的完整函数,这是这套源码里最核心的一块,后续任务队列就是反复调它:

struct SendResult { bool success; QString message; }; SendResult sendMessageToWindow(QAxObject* dm, const QString& windowTitle, const QString& text, const QString& sendBtnPicPath) { SendResult result; result.success = false; // 1. 每次发送前重新找窗口,防止句柄失效 long hwnd = findWindowHandle(dm, windowTitle); if (hwnd == 0) { result.message = QString("窗口未找到: %1").arg(windowTitle); return result; } // 2. 如果上次绑定失败,尝试重新绑定 QVariant bindRet = dm->dynamicCall("IsBind(int)", (int)hwnd); if (bindRet.toInt() != 1) { if (!bindWindow(dm, hwnd)) { result.message = "绑定窗口失败"; return result; } } // 3. 在窗口客户区范围里找“发送”按钮图片 int width = dm->dynamicCall("GetClientWidth()").toInt(); int height = dm->dynamicCall("GetClientHeight()").toInt(); QList<Point> btnPoints; if (!findPicInWindow(dm, sendBtnPicPath, 0, 0, width, height, btnPoints)) { result.message = "发送按钮定位失败"; return result; } // 4. 以按钮上方 60 像素的位置近似当输入框,也可改成找字定位 Point sendBtn = btnPoints.first(); int inputY = sendBtn.y - 60; // 5. 执行点击 + 粘贴 + 发送 if (!sendTextByClipboard(dm, text, sendBtn.x, inputY, sendBtn.x, sendBtn.y)) { result.message = "文本发送失败"; return result; } result.success = true; result.message = "OK"; return result; }

这个函数的容错点都在注释里了。每次发送前重新 FindWindow 是为了对抗“窗口被用户手滑关掉再重开”的情况;IsBind 检查是避免重复绑定导致大漠内部状态错乱。输入框用按钮上方固定偏移来近似,是个偷懒但实用的做法,真正常见的聊天窗口里输入框和发送按钮的垂直距离变化不大。如果按钮是动态位置,把第 3 步改成 3.1 节的 FindStr 找“发送”二字即可。

3.4 节奏控制:别把自动发消息写成“秒刷”

代码跑通之后,最容易忽略的是发送节奏。大漠的 MoveTo 和 LeftClick 是真实鼠标操作,程序里如果连续执行没有停顿,第一目标窗口会卡消息,第二也容易被目标软件的风控盯上。常见做法是引入随机延迟,让每次操作之间的间隔落在一个人工操作的合理范围内。

int randomDelay(int baseMs, int jitterMs) { // 在 base 到 base+jitter 之间随机,模拟人手操作的不规律性 return baseMs + QRandomGenerator::global()->bounded(jitterMs); }

调用时比如点击输入框后QThread::msleep(randomDelay(60, 40)),粘贴完成等randomDelay(120, 80)),每条消息之间的长间隔用randomDelay(1500, 1500))兜底。这个思路比写死 sleep 更好调试——把 base 调低一点就是压力测试模式,调高一点就是慢速安全模式,不需要改代码结构。大漠 3.1233 的前台绑定模式决定了操作时窗口必须可见,所以节奏设计还要考虑目标窗口消息积压的卡顿,间隔太短会导致上一次点击还没被目标程序处理完就发了下一次坐标,那个场景下发出去的消息会丢。

4. 让自动发消息变成能跑整夜的任务队列:线程、日志与异常恢复

4.1 消息队列与工作线程:不要让界面卡死

单条发送函数跑通后,下一步就是把一列表的待发消息按顺序执行。这一步的核心设计决策是:消息队列绝不能跑在 GUI 线程里,否则 Qt 界面会失去响应,看起来像程序死了。常见做法是写一个继承QThread的 Worker,里面放一个QQueue<OutgoingMessage>,线程启动后循环取队列,逐条调用 3.3 节的发送函数。

class AutoSenderWorker : public QThread { Q_OBJECT public: struct Task { QString windowTitle; QString content; int intervalMs; // 该任务发送后的冷却时间 }; explicit AutoSenderWorker(QObject* parent = nullptr) : QThread(parent) {} void addTask(const Task& task) { QMutexLocker locker(&m_mutex); m_queue.enqueue(task); } void stopSoon() { QMutexLocker locker(&m_mutex); m_stopFlag = true; } signals: void taskFinished(int index, bool ok, const QString& detail); void allTasksDone(); protected: void run() override { while (true) { QMutexLocker locker(&m_mutex); if (m_stopFlag) break; if (m_queue.isEmpty()) { locker.unlock(); msleep(200); // 空转,避免线程死循环占满 CPU continue; } Task task = m_queue.dequeue(); locker.unlock(); // 这里执行自动发送,dm 对象在工作线程内单独创建 QAxObject* dm = createDmInstance(nullptr); SendResult r = sendMessageToWindow(dm, task.windowTitle, task.content, m_sendBtnPic); emit taskFinished(++m_doneCount, r.success, r.message); delete dm; msleep(task.intervalMs); } emit allTasksDone(); } private: QQueue<Task> m_queue; QMutex m_mutex; bool m_stopFlag = false; int m_doneCount = 0; QString m_sendBtnPic; };

这段代码里每个任务都重新创建和销毁大漠对象,看着浪费,实际是故意的:大漠组件在长时间运行后偶尔会进入“所有接口返回 -1”的假死状态,重新创建对象能规避掉这个经典问题。sendMessageToWindow 内部已经做了窗口查找和绑定,所以队列里只管给窗口标题,不保存句柄,句柄永远是用的时候现查,这也让“窗口中途被手动关掉再打开”的情况能自动恢复。

任务间隔 task.intervalMs 不能直接用 3.4 节的随机函数——实际使用中你会发现,整夜跑任务时真正需要的间隔是“发送一条 + 等待目标窗口响应 + 下一条”,所以每个 Task 里单独存间隔,界面可以按任务类型配置。等待机制用msleep已经够了,不要去写信号量等待目标窗口回应,那种方案在无人值守场景里容易死锁。

4.2 日志与发送成功验证:用截图证明“真的发出去了”

队列跑起来后,下一个问题就是“它到底发出去了没有”。靠发送函数的返回值不够——大漠的 LeftClick 和 KeyPress 只代表鼠标键盘事件发出去了,不代表目标程序真的把消息送进了会话。这时候要用大漠的 Capture 截图来验证:发送前截一张输入框区域,发送后等一秒再截一张,比较两张图的差异。如果输入框内容被清空了,基本可以判定消息发送成功;如果截图完全一致,说明点击根本没起作用,需要记一条失败日志。

bool verifySentByCapture(QAxObject* dm, int x, int y, int w, int h, const QString& beforePic, const QString& afterPic) { // 发送前截图保存到临时文件 dm->dynamicCall("Capture(int,int,int,int,QString)", x, y, x + w, y + h, beforePic); // 发送后截图 dm->dynamicCall("Capture(int,int,int,int,QString)", x, y, x + w, y + h, afterPic); // 用大漠的 CmpPic 比对两张图,相似度低于阈值说明内容发生了变化 QVariant ret = dm->dynamicCall( "CmpPic(QString,QString,double)", beforePic, afterPic, 0.9); return ret.toInt() == 0; // 返回 0 表示不相似,说明输入框内容变了 }

这里截图区域选输入框那一行,比对相似度阈值 0.9。这个函数不要放在每个任务里都跑,截图和比对都耗时,任务密集时反而拖慢节奏;我一般会做成“每 10 条任务抽验 1 次”的采样验证,验证失败时把这 10 条全部标记为待复核,重新加入队列。日志记录则用 Qt 的 qInfo 输出到文件,每条日志包含时间戳、目标窗口标题、消息摘要、发送结果、抽验结果。不要在这里记完整消息原文,日志文件一旦泄露,里面全是敏感内容,记前二十个字符做摘要就够了。

4.3 异常恢复:句柄失效、绑定冲突、连续失败自动停线

跑整夜任务最常见的三种异常,处理策略各不相同。

第一种是窗口句柄失效,表现为 FindWindow 返回 0 或者 IsBind 返回 -1。处理策略是重试三次,每次间隔五秒,三次都失败就跳过当前任务,进入下一个。不要无限重试同一个窗口,万一是目标程序被关了,后面所有任务都会堵死。

第二种是绑定冲突,表现为 BindWindow 返回 -2 或 0,通常是大漠组件还没有解绑上一个窗口就绑新的。处理策略是在 bindWindow 之前先调用UnBindWindow()解绑,然后再绑。大漠 3.1233 免费版同一时刻只允许一个绑定窗口,这个约束在队列场景里尤其重要。

第三种是连续失败需要自动停线,防止异常状态下把消息重复发出去造成事故。做法是在 Worker 里记一个连续失败计数器:

int m_consecutiveFails = 0; // 在 run() 循环的发送结果处理中 if (!r.success) { m_consecutiveFails++; if (m_consecutiveFails >= 5) { qCritical() << "连续 5 次失败,自动停止任务队列"; emit allTasksDone(); break; } } else { m_consecutiveFails = 0; }

连续失败 5 次就停线,这是我从一次真实事故里学来的阈值:某个凌晨窗口对话框弹了个广告把发送按钮挡住了,自动脚本连着发了 40 多条同样的消息进错误会话,最后被目标软件风控踢下线。加了这个保护后,最差情况也就是重复 4 条,属于可解释的范围。停线后由人工介入查看日志和截图,再决定是重跑失败任务还是调整参数。

5. Qt + 大漠插件踩坑记录:五条血泪经验

5.1 fatal: cannot mix incompatible qt library (version ex50601) with this libra

现象:编译链接时报错fatal: cannot mix incompatible qt library (version ex50601) with this library,程序直接起不来。

原因:这条错误出现在 Qt 库版本混用场景。常见做法是机器上装了多个 Qt 版本,环境变量 PATH 先命中了一个老版本或者不同编译器的 Qt DLL。version ex50601 代表 Qt 5.6.1 或类似版本的内部版本号,目标工程是用另一个 Qt 编译的导出库,链接器发现两边 QT 版本号对不上就拒绝工作。

解决:在 Qt Creator 里检查当前 Kit 的 Qt 版本是否和代码里引用的头文件一致,尤其注意是不是把 5.x 和 6.x 混用了。命令行编译的话,删除用户 PATH 里所有 Qt bin 目录,让出 IDE 自己带的 Qt 路径。另外确认 Qt 程序位数是 32 位(大漠 3.1233 是 32 位组件),位数不一致一样会在动态加载时报相似错误。

5.2 大漠组件创建失败:dm.dmsoft 创建出来是 Null

现象:new QAxObject("dm.dmsoft", parent)之后isNull()返回 true,Ver() 根本调不了。

原因:最常见是 dm.dll 没有注册成功,或者注册时用了 64 位 regsvr32 去注册 32 位 DLL。另一个隐蔽原因是 Qt 程序是 64 位,无法加载 32 位进程内 COM 组件,QAxObject 直接创建失败。

解决:用管理员权限跑regsvr32 D:\project\vendor\dm\dm.dll,注意确认弹出的是成功提示而不是“已加载但未注册”。然后把 Qt Kit 切到 32 位,重新构建整个工程。验证注册是否成功的命令是去注册表HKEY_CLASSES_ROOT\dm.dmsoft下看有没有子键,没有就说明注册没生效。

5.3 绑定窗口返回 -1 / -2:窗口找得到,绑不上

现象:FindWindow 能拿到非零句柄,但 BindWindow 返回 -1 或 -2,消息完全发不出去。

原因:-1 通常表示窗口句柄无效或窗口已销毁,-2 表示当前已有其他窗口被绑定,必须先解绑。还有一个容易忽略的情况是目标程序以管理员权限运行,而你的 Qt 程序没有提权,这样 FindWindow 拿到句柄但 BindWindow 没有权限操作,返回也会失败。

解决:代码里每次绑定前先无条件调一次UnBindWindow(),忽略它的返回值。然后以管理员身份重新运行 Qt 程序。如果目标程序是游戏还涉及保护驱动,这种场景 3.1233 免费版基本无能为力,只能换其他方案,不要在这上面硬耗。

5.4 中文消息乱码或者粘成旧内容

现象:发送条数多了之后,某几条消息内容跟预期不符,出现乱码或者发出去的是上一条消息的残余。

原因:SetClipboard 写入文本后立即执行 KeyPress(17,86) 粘贴,剪贴板还没就绪,大漠读到了上一次的旧内容。乱码则是 Qt 字符串到 COM 调用之间编码转换出错,常见于 3.1233 对大漠内部 ANSI 编码的兼容问题。

解决:SetClipboard 之后至少加 50 毫秒延迟,再执行粘贴,这在 3.2 节代码里已经有了。编码问题则统一在发送前把 QString 经toLocal8Bit()转一次再传进 dynamicCall,或者在剪贴板写入后重新读一次验证内容一致再粘贴。另外输入法在中文状态时会干扰 Ctrl+V 的传递,自动发送期间用 Qt 调用系统 API 把输入法切成英文模式,这是很多老教程没提的坑。

5.5 程序打包到别的机器:qt.qpa.plugin 找不到 linuxfb / 组件起不来

现象:把编译好的程序拷贝到另一台 Windows 机器,双击运行弹qt.qpa.plugin: could not find the qt platform plugin "linuxfb",或者大漠组件创建失败。

原因:这个错误字面是 Linux 嵌入式平台的,但实际是 Qt 在目标机器上找不到对应平台的 QPA 插件。发布时只拷了 exe,没拷platforms目录下的qwindows.dll;而大漠创建失败则几乎总是目标机器没注册 dm.dll 或者是 64 位系统但程序是 32 位、依赖库缺失。

解决:发布时用windeployqt把 Qt 运行库完整打进去——在命令行执行windeployqt --release --no-system-dlls your_app.exe,确认生成目录里有platforms/qwindows.dll。然后把 dm.dll 和注册脚本放进安装包,首次运行执行一次注册。3.1233 免费版没有数字签名,杀毒软件容易拦截 DLL 加载,建议在打包说明里明确告知使用者添加白名单,不然十台机器里能有三四台静默失败。

6. 进阶:从固定话术到带状态的自动回复助手

当“定时批量发送”跑稳定之后,这套 Qt + 大漠 3.1233 的骨架可以继续往上加状态检测,变成自动回复助手。思路是开一个定时器,每隔两三秒对目标窗口的接收区域做一次 FindStr 找特征文字,比如“收到”“在吗”这类关键词,找到就触发 3.3 节的发送函数回一条预设回复,回完把关键词写进一个已处理列表,避免同一句话被反复触发。

实现时要给 Worker 加一个“监听模式”,和队列模式互斥。监听模式下不消费任务队列,只是定时截图+找字+发消息,类似一个循环状态机:空闲 -> 检测到关键词 -> 延迟随机 2 到 5 秒(模拟人工看到再回复的节奏)-> 调用发送 -> 回到空闲。这个延迟很关键,查找频率和延迟都要可配置,不然自动回复被目标软件当成外挂封号是迟早的事。

验证整套源码是否可靠,我会跑一个 100 条消息的压测:每条记录发送时间、发送结果、抽验结果,最后统计失败率和平均耗时。失败率低于 2% 算合格,超过了就去看日志里是 FindWindow 失败多还是绑定失败多——前者说明目标窗口不稳定,后者说明绑定模式选错了。这套验证办法同样适用于生产环境上线前的回归测试。

说句实话,大漠 3.1233 这套接口看着老,但它在 Windows 前台自动化这个窄场景里依然比很多新框架省心。我的习惯是每个新目标窗口第一次接入时,先花十分钟手动跑一遍找字坐标再写进配置,后面基本就不会再翻车。自动化脚本最忌讳“能用就行”的心态,窗口标题、按钮图片、发送间隔这些边界条件都验证过,才能安心让它通宵跑。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询