简介:一份基于Qt框架调用大漠插件3.1233的C++源码工程,专为需要实现自动发送消息、模拟键鼠操作和批量辅助处理的开发者准备。项目提供完整的可视化界面与可编译工程,内置微信自动发消息演示程序,适合学习Qt动态链接DLL、调用Windows底层API以及编写自动化脚本的读者。压缩包共68个文件,约21.97MB,涵盖24个dll动态库、7个exe演示程序、4个cpp源文件、3个h头文件,以及qm翻译文件、chm中文手册、ui界面和pro工程文件等,目录结构清晰,便于对照练习。大漠插件3.1233为免费版本,附带中文文档,降低了非英语用户的使用门槛;源码中实现了从加载插件、定位窗口、模拟输入到发送消息的完整逻辑,并包含异常处理与调试思路。目前已有4870人学习下载,工程附带发布目录与运行所需的全部动态库,可快速体验自动发消息效果,适合作为自动化测试、批量消息处理等场景的参考实现。
1. Qt 里跑大漠插件 3.1233:先把自动发消息这件事拆明白
在 Windows 上做自动化消息推送,方案其实不少:按键精灵、Python pyautogui、直接写 Win32 消息循环。但你一旦接过真实的批量通知、客服话术分发这类需求,就会发现它们要么收费、要么依赖运行环境太娇气,要么连“绑定后台窗口”都做不到。这套 Qt 结合大漠插件 3.1233 的源码包,正好卡在了一个很实用的位置:Qt 负责界面和业务逻辑,大漠插件负责模拟鼠标键盘、绑定窗口、找色找图,两边各干各的,配合起来比纯脚本稳得多。它适合已经在用 C++ 做 Windows 工具、想给程序加上自动化操作能力的人。这篇笔记我会从调用方式讲到窗口绑定、消息发送,再落到发布和踩坑,照着走完你就能把示例工程跑成自己的自动发消息小工具。
2. 大漠插件接入 Qt 的两种姿势:COM 注册与动态加载
2.1 为什么大漠 3.1233 不是普通 DLL:先搞懂它的调用模型
拿到这份源码,最直观的疑问是:大漠插件到底怎么调?很多人第一反应是像普通 C 接口 DLL 那样,在 pro 文件里LIBS += dm.lib,然后在 C++ 里直接调dm.FindWindow()。这个思路对大部分插件成立,但大漠 3.1233 不是这么玩的。它是一个 COM 组件,以dm.dll形式存在,对外暴露的是 IDispatch 接口。你在 C++ 里没法直接拿到导出函数表,必须走 COM 的创建流程。
用 Qt 来调它,最省事的路径就是 QAxObject,它是 Qt 官方封装好的 ActiveX/COM 客户端。代码量比手写CoCreateInstance少得多,还自动处理了 IDispatch 的调用细节。看一下典型启动代码:
#include <QAxObject> #include <QDebug> // 在 Qt 中启动大漠插件(方式一:COM ProgID) QAxObject* dm = new QAxObject(); if (!dm->setControl("dm.dmsoft")) { qDebug() << "大漠插件加载失败,确认 dm.dll 已注册"; return; } // 调用大漠的 Ver() 方法验证连通性 QString ver = dm->dynamicCall("Ver()").toString(); qDebug() << "大漠版本:" << ver;setControl("dm.dmsoft")里的dm.dmsoft是 ProgID,它在dm.dll被regsvr32注册后写入注册表。dynamicCall负责把函数名和参数打包成 COM 调用。第一步一定要先确认Ver()能返回版本号,否则后面所有功能都会静默失败或崩溃。这一步通了,就说明 Qt 进程和插件之间搭上线了,后续 BindWindow、SendString 都是同一个调用套路。
2.2 不注册也能调:LoadLibrary 配合 COM 导出的备选路线
如果你的环境不允许动不动就regsvr32(比如办公电脑没管理员权限),还有一条路线:直接用LoadLibrary把dm.dll载入进程,再通过 COM 接口手动拉函数入口。这种做法的核心在于,即便不写注册表,COM 类工厂的导出函数也是可以按名称找出来的。
#include <windows.h> typedef HRESULT (__stdcall *DllGetClassObjectFunc)(REFCLSID, REFIID, LPVOID*); // 手动加载 dm.dll 并创建 COM 对象(方式二:LoadLibrary) HMODULE hDll = LoadLibraryW(L"C:\\dm3\\dm.dll"); if (!hDll) { qDebug() << "加载 dm.dll 失败,检查路径"; return; } DllGetClassObjectFunc pfnGetClassObj = (DllGetClassObjectFunc)GetProcAddress(hDll, "DllGetClassObject"); // 拿到类工厂后,用 IClassFactory::CreateInstance 创建 dm 对象, // 再 QueryInterface 到 IDispatch 用。这一步流程固定,不再展开。这条路线严格说是“绕过注册表直接启动 COM 服务器”,不是把大漠当普通 DLL 调。坑在于:你得手动查注册表拿到 CLSID、自己写IClassFactory重复劳动,而且进程位数必须和当前 Qt 编译位数一致。绝大多数情况下,你拿到这份源码里配的dmobject.h、dmobject.cpp就是帮你把这些 COM 细节包好的中间层——它内部已经封装好了创建、释放和常见接口声明,你只需要在 Qt 工程里把这两个文件加进去,再配合QAxObject使用即可。我的建议是:开发阶段用方式一先跑通,确实有部署层面的注册限制再换方式二,别一上来就钻底层。
3. 自动发消息功能实现:从窗口绑定到点击发送
3.1 先定位目标窗口:FindWindow 与 BindWindow 的配合
自动发消息的第一步不是发,而是锁定目标窗口。很多人直接按屏幕坐标点击输入框,这套方案在窗口被遮挡、最小化、或者屏幕分辨率换过之后就全废了。大漠的做法更稳:用FindWindow拿到窗口句柄,再用BindWindow建立后台绑定关系。所谓“后台绑定”,核心价值是窗口不在最前面、甚至最小化时,模拟的鼠标键盘消息仍然能直接投递到目标窗口内部,而不是真的去动系统光标。
// 1. 按窗口标题查找目标句柄 HWND hwnd = ::FindWindowW(nullptr, L"目标聊天窗口标题"); if (hwnd == nullptr) { qDebug() << "没有找到目标窗口,确认标题正确,且窗口未最小化到托盘"; return; } // 2. 绑定窗口:显示模式 gdi,鼠标模式 windows,键盘模式 windows QString bindRet = dm->dynamicCall( "BindWindow(long, string, string, string, long)", (long)hwnd, "gdi", "windows", "windows", 0); qDebug() << "绑定结果(1 为成功):" << bindRet;BindWindow的五个参数依次是:窗口句柄、显示模式、鼠标模式、键盘模式、附加模式。gdi表示用 GDI 方式抓取窗口画面,兼容性最好;windows表示直接用 Windows 消息模拟鼠标键盘,效率高但个别游戏或自绘界面不吃这套。遇到绑定返回 0 或负数时,把gdi换成dx、windows换成dx逐个试,总有一组能成。记住:绑定成功之前,不要做任何发送动作,不然消息会发到当前前台窗口,翻车概率极高。
3.2 文本写入的两种姿势:SendString 与剪贴板方案
窗口绑定好以后,接下来的操作是把文本塞进输入框。大漠提供了SendString系列方法,但我在实际使用中遇到过两个问题:一是中文文本可能被转成 ANSI 后乱码,二是部分输入框(尤其自绘界面)不响应模拟按键。所以工程里最稳的做法准备了两套方案,根据目标窗口类型切换:
// 方案 A:大漠 SendString,适合英文数字和标准 Windows 控件 dm->dynamicCall("SendString(long, string)", (long)hwnd, content.toStdString().c_str()); // 方案 B:剪贴板 + Ctrl+V,适合中文和自绘输入框 QGuiApplication::clipboard()->setText(content); // 先模拟一次 Ctrl+V 粘贴 dm->dynamicCall("KeyPress(long, long)", 17, 1); // Ctrl dm->dynamicCall("KeyPress(long, long)", 86, 1); // V方案 B 的背后逻辑是:剪贴板是系统级的,任何窗口的粘贴功能都能拿到数据,比逐字模拟按键可靠得多。不过要注意,KeyPress模拟的是真实物理按键,如果目标窗口绑定的是后台模式,部分窗口不会响应真实键。这时可以改绑鼠标键盘模式为dx,或者干脆用SendStringIme(大漠输入法模式)尝试。我一般会把两个方案做成配置项,让用户在界面上选,而不是写死在代码里——因为同一个软件在不同聊天工具上的表现差别很大,你没法预判。
3.3 带界面的定时循环发送:把 QThread 用对
源码包里带了mainwindow.ui,说明设计上是有界面的版本:用户在输入框里填内容、设次数、设间隔,然后程序按计划跑。既然是循环发送,有个关键约束必须遵守:不能在 UI 线程里做 Sleep 等待,否则界面会卡死,按钮点不动,窗口无法拖拽。正确姿势是把发送逻辑丢到一个 QThread 工作线程里,通过信号槽和界面通信。
// 工作线程头文件关键声明 class Worker : public QThread { Q_OBJECT public: void setPayload(const QString& text, int count, int intervalMs); void stop(); // 请求停止 protected: void run() override; private: QString m_text; int m_count; int m_intervalMs; QAtomicInt m_stopFlag; // 原子变量,跨线程安全 }; // run() 里的核心循环 void Worker::run() { for (int i = 0; i < m_count; ++i) { if (m_stopFlag.loadAcquire()) break; // 检查停止请求 // 方案 A 或 B 发送文本 emit messageSent(i + 1, m_count); msleep((unsigned long)m_intervalMs); // QThread::msleep,不卡 UI } emit finished(); }QAtomicInt是这里容易忽略的点:它是原子操作,保证跨线程读写安全,不能直接用一个bool stopFlag来代替,否则编译器优化可能导致线程一直读不到新值。msleep用的是毫秒,界面上的「间隔(秒)」要记得乘以 1000。另外,在工作线程里直接调用 QAxObject 的dynamicCall是安全的,只要这个对象是在该线程里创建或已经跨线程注册过(通过moveToThread或直接线程内 new)。用QProcess调用外部程序那种做法在这里没必要,因为大漠本来就是进程内 COM,效率高得多。
4. 运行时避坑记录:从 0x0000005 到 Qt5Core 版本混用
4.1 运行几秒后崩溃,提示 0x0000005 访问冲突
现象:程序启动正常,一调用大漠方法就崩,有时甚至直接闪退,事件查看器里记录0xc0000005(访问违规)。
原因:位数不匹配是第一名。大漠 3.1233 免费版是老 32 位 DLL,而 Qt Creator 默认的 MSVC 套件可能是 x64。32 位 COM 组件被加载进 64 位 Qt 进程,COM 层勉强能过,但接口调用时堆栈和参数传递方式不一致,迟早访问到非法地址。
解决:把整个 Qt 工具链切换到x86(或者叫MSVC 2019 32bit/MinGW 32bit)套件,重新编译。验证方法:编译产物用 dumpbin 或排查工具看一眼是x86还是x64,确保和dm.dll一致。这也是我拿到任何插件类源码后第一个检查项,能省下大量玄学排错时间。
4.2 QAxBase 报错:Error calling IDispatch 或 找不到类名
现象:setControl("dm.dmsoft")返回 false,或者在dynamicCall时抛QAxBase::setControl: no such control。
原因:大漠的 COM 注册信息缺失。dm.dmsoft这个 ProgID 需要先通过regsvr32把dm.dll登记到注册表。换过电脑、换过目录,或者杀毒软件清理过注册表,都会导致这个问题。
解决:以管理员身份打开 CMD,执行注册命令:
regsvr32 /s "C:\YourPath\dm.dll"注册成功后用注册表编辑器搜索dm.dmsoft,能看到 CLSID 和 InprocServer32 路径就说明已生效。注意大漠 3.1233 在 64 位系统上可能还需要 WOW6432Node 下的注册条目,regsvr32一般会自动处理,但如果注册后仍然报错,检查一下系统是 32 位还是 64 位版本再判断。
4.3 换电脑运行提示 could not find the qt platform plugin windows
现象:exe 在自己电脑跑得好好的,拷到别的机器上双击没反应,或者弹qt.qpa.plugin: could not find the qt platform plugin "windows" in...。
原因:Qt 是插件化架构,qwindows.dll必须放在 exe 同级的platforms目录下,同时Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll必须在这个目录能找到。你只拷了 exe 和业务 dll,漏掉了整个 Qt 运行时骨架。
解决:在发布机上用windeployqt补齐运行环境。工程编译输出目录里找到 exe,执行:
windeployqt 自动发消息.exe这条命令会自动生成platforms、styles、imageformats、translations等目录,并拷贝对应的 Qt5 系列 DLL。发布时把这整包带上,而不是盯着单个 dll 拷,这才是 Qt 发布的标准姿势。
4.4 编译或运行时提示 cannot mix incompatible qt library 版本混用
现象:某个 exe 加载时报cannot mix incompatible qt library (version ...) with this library,或者运行到一半莫名其妙崩溃。
原因:不同版本的 Qt5 动态库混在同一个目录里。比如你按网上教程把旧项目的Qt5Core.dll(比如 5.6 的)拷进了当前 5.15 构建的目录,Qt 加载时会比对核心库版本,一旦发现不一致立刻拒绝运行。
解决:一个应用目录里只放一套 Qt 运行时,版本必须和编译时的套件完全一致。最稳妥的做法是全部用windeployqt生成,x86 项目的运行时和 x64 项目严格分开,目录下不要混用任何手拷的 Qt DLL。这条我踩过一次,排查了一整天最后发现是Qt5Core.dll被覆盖成了老版本。
4.5 BindWindow 总是返回 0 或负数
现象:FindWindow能找到句柄,但BindWindow的返回值不是 1,换了很多参数组合也不成功。
原因:三种典型情况——窗口句柄虽然存在但实际窗口已销毁(句柄失效);目标窗口以管理员权限运行,你的 Qt 程序是普通权限,无法向高权限窗口注入消息;窗口是特殊系统窗口或桌面窗口,不允许后台绑定。
解决:先把FindWindow的返回值打印出来,确认句柄在绑定前仍然有效。然后用“排障三角组合”逐项测试:把后台模式依次换成gdi、dx、dx2,鼠标键盘模式换成windows、dx,每次只改一个参数。如果还不行,把 Qt 程序也用管理员身份运行(在 exe 属性的兼容性里勾选“以管理员身份运行此程序”),权限对等后大部分窗口都能绑上。
5. 把源码包跑起来:工程文件结构与发布配置
5.1 dmDemo.pro 里必写的配置项
拿到dmDemo(AutotMess).rar解压后,第一件事是打开dmDemo.pro确认工程配置齐全。大漠插件是通过 COM 调用的,Qt 侧必须启用 ActiveQt 模块;如果你用的是 QAxObject 方式,对应的是axcontainer模块。漏掉这一行,编译会直接报QAxObject: No such file or directory。
QT += core gui widgets axcontainer CONFIG += c++11 TARGET = AutoMessageTool TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp \ dmobject.cpp HEADERS += mainwindow.h \ dmobject.h FORMS += mainwindow.uiQT += axcontainer引入了 QAxObject 和 QAxWidget 支持。widgets是因为 mainwindow.ui 是基于 Widgets 体系的。如果你的工程是纯控制台程序,可以把gui widgets去掉,但界面版必须保留。工程文件里不需要额外链接dm.dll的导入库,这是它和普通 DLL 最大的差异——COM 调用不靠.lib链接。
5.2 大漠插件文件怎么放:dm.7z 解压后的目录规划
压缩包里的dm.7z解压后,里面是dm.dll以及配套的中文手册(大漠免费版 3.1233 自带手册,不必去逐个查英文文档)。这个文件不需要放进 Qt 源码工程目录参与编译,你只需要保证运行的时候程序能找到它。
推荐的做法:在工程根目录建一个vendor/dm/文件夹,把dm.dll放进去,然后在main.cpp里设置当前工作目录为 exe 所在目录后,用绝对路径或环境变量绑定插件路径:
#include <QCoreApplication> #include <QDir> int main(int argc, char *argv[]) { QApplication app(argc, argv); // 让程序的工作目录始终指向 exe 所在目录, // 这样无论从哪个路径启动,都能相对定位 dm.dll QDir::setCurrent(QCoreApplication::applicationDirPath()); // 如果 dm.dll 所在目录不在系统 PATH 中, // 通过这种方式加入搜索路径,COM 加载时会按此路径查找 QString dmPath = QDir::current().absoluteFilePath("vendor/dm"); qputenv("PATH", qgetenv("PATH") + ";" + dmPath.toLocal8Bit()); }这个细节很实用:在 Qt Creator 里调试时工作目录默认是构建目录,直接运行生成的 exe 时工作目录却是 exe 所在目录,两处不一致会导致调试正常、独立运行就加载不到dm.dll。强制setCurrent可以抹平这个差异,属于发布期必做的一步。
5.3 windeployqt 发布与运行时目录核对
源码包内置了发布程序目录,里面已经出现了iconengines、imageformats、platforms、styles、translations等文件夹,以及libEGL.dll、libGLESV2.dll、D3Dcompiler_47.dll、opengl32sw.dll这些 DLL。这些不是大漠插件带的,而是 Qt 的运行时依赖。
libEGL.dll、libGLESV2.dll和opengl32sw.dll是 Qt 的 OpenGL 动态渲染链路,缺了会导致界面黑屏、控件绘制不全;platforms目录提供 Windows 平台插件;imageformats负责图标和图片格式解码;styles提供 Windows 风格渲染。如果发布时漏掉其中任何一组,常见的症状是“双击 exe 无反应”或“界面闪烁后退出”——而不是报具体缺哪个 DLL,排查起来很隐蔽。
# 在编译输出目录执行(MSVC 版 Qt) C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe 自动发消息.exe # MinGW 版 Qt 的部署工具路径略有不同 C:\Qt\5.15.2\mingw81_64\bin\windeployqt.exe 自动发消息.exe跑完后,再把vendor/dm目录里的dm.dll复制到 exe 同目录(或者保持相对路径的引用关系),整个发布包就完整了。我习惯在发布前做一次“干净环境验证”:把整包拷贝到一个没装 Qt 的虚拟机或同事电脑上跑一遍,确认能正常弹窗、能绑窗、能发消息。虚拟机能跑通,交付才算数。
6. 把发消息逻辑抽成独立模块:封装、验证与试跑顺序
源码包里dmobject.h和dmobject.cpp的价值不只是“能用”,它给你提供了一种可复用的组织方式:把大漠的所有操作收敛到一个类里,UI 层只跟这个类打交道。我看过很多同类源码,把dynamicCall直接散落在 mainwindow.cpp 的按钮槽函数里,结果是每增加一个功能就要改一遍界面代码。更好的做法是定义成独立服务类,界面只管参数收集,一个send()方法完成整条链路。
下面是我提炼过的最小封装骨架:
class AutoMessenger : public QObject { Q_OBJECT public: bool init(); // 加载大漠 COM bool bindWindow(const QString& title); // 绑定目标窗口 bool sendMessage(const QString& text); // 发送单条消息 void release(); // 解绑和释放 COM 对象 private: QAxObject* dm = nullptr; HWND hwnd = 0; }; bool AutoMessenger::init() { dm = new QAxObject(); if (!dm->setControl("dm.dmsoft")) return false; return !dm->dynamicCall("Ver()").toString().isEmpty(); }这个封装的好处是,你可以在不碰 UI 的前提下先用一个控制台测试程序验证init -> bindWindow -> sendMessage三步是否通。我第一次拿到这类项目的时候图省事,直接在界面里一边调参数一边试,结果窗口没绑上都不知道是界面问题还是大漠调用问题。后来改成写一个 10 行左右的命令行测试入口,只跑核心三步,问题定位立刻清楚。
试跑顺序非常重要,正确的顺序是:先开一个记事本窗口,用测试程序绑定“无标题 - 记事本”,发送一串英文数字,看记事本里有没有内容。这一步过了,再换成带中文的内容,确认中文不乱码。最后才切换到真实目标窗口(如聊天工具),并且先发一条测试语,人工确认收到后,再放开循环次数和定时器。整个过程不要跳步,每跳一步,出问题时你就要同时怀疑大漠调用、编码、目标窗口控件三重因素。
另一个值得养成的习惯是把参数外置:发送内容、次数、间隔、目标窗口标题,不要写死在代码里,用配置文件或界面字段承载,这样不同场景只需要改配置,不用改代码重新编译。拿这次 Demo 来说,把发送内容和次数抽出来之后,它就不只是一个“自动发消息 demo”,而是一个可以接不同目标、不同策略的发送器骨架。从那以后我每次接手 Qt 自动化类代码,都强制走一遍“先跑通三步验证、再清干净目录发布、最后干净环境复测”的流程,翻车率确实低了很多,希望帮到你。
本文还有配套的精品资源,点击获取