尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多线程使用大漠插件的正确姿势

多线程使用大漠插件的正确姿势 大漠插件在多线程环境下的使用其核心挑战在于COM组件的线程模型和对象生命周期管理。根据参考资料大漠插件本身是线程不安全的COM组件其接口调用必须在创建它的线程内进行否则会引发访问冲突。因此多线程使用的关键在于线程隔离和消息泵的维护。一、多线程使用方案对比与选择方案核心原理优点缺点适用场景单线程独占任务队列主线程或专用线程持有大漠对象其他线程通过消息队列向其投递任务。实现简单完全避免多线程冲突稳定性高。所有操作串行化可能成为性能瓶颈任务响应有延迟。大多数自动化脚本、对并发要求不高的场景。多对象多线程线程隔离每个工作线程独立创建和持有自己的大漠对象对象间完全隔离。真正的并行操作性能高各线程任务互不影响。资源占用多每个线程一个对象线程管理复杂需确保每个线程都有消息循环。需要同时对多个窗口进行高频率、独立操作的场景。COM线程套间STA封装将大漠对象封装在独立的STA单线程套间线程中通过COM列集传递接口指针。COM标准做法理论最安全。实现极其复杂涉及列集/散集在自动化脚本中不实用。对COM线程模型有深入理解且要求极致稳定的复杂应用。结论对于绝大多数Qt结合大漠插件的应用推荐采用“单线程独占任务队列”方案因其在稳定性、复杂度和性能之间取得了最佳平衡。下文将重点阐述此方案的实现。二、核心实现单线程任务队列方案此方案架构如下一个专用的DMWorker线程持有大漠对象并运行事件循环其他线程通过信号槽将操作请求和参数发送给该线程由DMWorker线程顺序执行并返回结果。1. 定义任务结构与通信机制首先需要定义统一的任务格式用于封装不同的操作请求。// dm_task.h #ifndef DM_TASK_H #define DM_TASK_H #include QObject #include QVariant #include QVector // 任务类型枚举 enum class DMTaskType { Init, // 初始化插件 GetVersion, // 获取版本 FindWindow, // 查找窗口 Ocr, // 文字识别 MoveAndClick, // 移动并点击 // ... 可根据需要扩展 }; // 任务结构体 struct DMTask { DMTaskType type; qint64 taskId; // 任务唯一ID用于匹配结果 QVectorQVariant args; // 参数列表 QVariant result; // 执行结果 bool success; }; // 用于线程间传递任务的辅助类 class DMTaskWrapper : public QObject { Q_OBJECT public: explicit DMTaskWrapper(const DMTask task, QObject* parent nullptr) : QObject(parent), m_task(task) {} DMTask m_task; }; #endif // DM_TASK_H2. 实现大漠工作线程 (DMWorker)这个线程是核心它创建大漠对象并处理所有任务。// dm_worker.h #pragma once #include QThread #include QQueue #include QMutex #include QWaitCondition #include QTimer #include dm_task.h // 前向声明避免包含生成的dm.h带来的编译问题 struct Idmsoft; class DMWorker : public QThread { Q_OBJECT public: explicit DMWorker(QObject* parent nullptr); ~DMWorker(); // 提交异步任务返回任务ID qint64 submitTask(DMTaskType type, const QVectorQVariant args {}); signals: // 任务完成信号 void taskFinished(qint64 taskId, const QVariant result, bool success); // 线程内部错误信号 void workerError(const QString error); public slots: void onTaskSubmitted(DMTaskWrapper* wrapper); protected: void run() override; private slots: void processTaskQueue(); private: bool initDMObject(); // 初始化大漠COM对象 DMTask executeTask(const DMTask task); // 执行单个任务 Idmsoft* m_pDM; // 大漠对象指针 QQueueDMTaskWrapper* m_taskQueue; QMutex m_queueMutex; QWaitCondition m_queueNotEmpty; QTimer* m_processTimer; volatile bool m_stopped; };// dm_worker.cpp #include dm_worker.h #include QCoreApplication #include QLibrary #include QDebug // 必须在包含Qt头文件前定义解决编译冲突 #define Q_DECL_CONSTEXPR #define Q_DECL_RELAXED_CONSTEXPR // 包含生成的大漠接口头文件 #include dm.h // 定义必要的GUID和函数指针 typedef HRESULT(__stdcall* pDllGetClassObject)(REFCLSID, REFIID, LPVOID*); static const CLSID CLSID_dm { 0x26037A0E, 0x7CBD, 0x4FFC,{ 0x93, 0x94, 0xE4, 0x5D, 0x33, 0x95, 0x37, 0x40 } }; static const IID IID_Idmsoft { 0x8274D9F0, 0x6F34, 0x4C6D,{ 0x9E, 0x66, 0x78, 0x93, 0x7D, 0x8E, 0x7C, 0x8A } }; DMWorker::DMWorker(QObject* parent) : QThread(parent), m_pDM(nullptr), m_stopped(false) { m_processTimer new QTimer; m_processTimer-moveToThread(this); // 计时器要移动到工作线程 } DMWorker::~DMWorker() { m_stopped true; quit(); wait(); // 等待线程结束 if (m_processTimer) { delete m_processTimer; } } qint64 DMWorker::submitTask(DMTaskType type, const QVectorQVariant args) { static std::atomicqint64 s_taskId(0); qint64 newId s_taskId; DMTask task{ type, newId, args, QVariant(), false }; // 创建包装器并通过信号槽发送到工作线程线程安全 DMTaskWrapper* wrapper new DMTaskWrapper(task); wrapper-moveToThread(this); // 关键将对象所有权转移到工作线程 QMetaObject::invokeMethod(this, onTaskSubmitted, Qt::QueuedConnection, Q_ARG(DMTaskWrapper*, wrapper)); return newId; } void DMWorker::onTaskSubmitted(DMTaskWrapper* wrapper) { QMutexLocker locker(m_queueMutex); m_taskQueue.enqueue(wrapper); m_queueNotEmpty.wakeOne(); // 通知工作线程有新的任务 } void DMWorker::run() { // 1. 初始化COM库STA模型这是工作线程必须做的 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // 2. 初始化大漠对象 if (!initDMObject()) { emit workerError(Failed to initialize DM plugin object.); CoUninitialize(); return; } // 3. 启动定时器处理任务使用事件循环方式 m_processTimer-setInterval(0); // 尽可能快地处理 connect(m_processTimer, QTimer::timeout, this, DMWorker::processTaskQueue, Qt::DirectConnection); m_processTimer-start(); // 4. 进入线程事件循环 exec(); // 这是关键保持线程活跃并响应信号/槽和定时器 // 5. 清理 m_processTimer-stop(); if (m_pDM) { m_pDM-Release(); m_pDM nullptr; } CoUninitialize(); } void DMWorker::processTaskQueue() { DMTaskWrapper* wrapper nullptr; { QMutexLocker locker(m_queueMutex); if (m_taskQueue.isEmpty()) { return; } wrapper m_taskQueue.dequeue(); } if (wrapper m_pDM) { DMTask executedTask executeTask(wrapper-m_task); emit taskFinished(executedTask.taskId, executedTask.result, executedTask.success); } delete wrapper; // 删除任务包装器 } bool DMWorker::initDMObject() { QLibrary dmLib(dm.dll); if (!dmLib.load()) { qDebug() Load dm.dll failed: dmLib.errorString(); return false; } pDllGetClassObject getClassObj (pDllGetClassObject)dmLib.resolve(DllGetClassObject); if (!getClassObj) { qDebug() Resolve DllGetClassObject failed.; return false; } IClassFactory* pCF nullptr; HRESULT hr getClassObj(CLSID_dm, IID_IClassFactory, (void**)pCF); if (FAILED(hr) || !pCF) { qDebug() Get class factory failed. HRESULT: QString::number(hr, 16); return false; } hr pCF-CreateInstance(nullptr, IID_Idmsoft, (void**)m_pDM); pCF-Release(); if (FAILED(hr) || !m_pDM) { qDebug() Create dm object failed. HRESULT: QString::number(hr, 16); return false; } // 验证对象是否创建成功 BSTR ver nullptr; hr m_pDM-Ver(ver); if (SUCCEEDED(hr)) { qDebug() DM Plugin initialized. Version: QString::fromWCharArray(ver); SysFreeString(ver); return true; } else { m_pDM-Release(); m_pDM nullptr; return false; } } DMTask DMWorker::executeTask(const DMTask task) { DMTask resultTask task; resultTask.success false; switch (task.type) { case DMTaskType::GetVersion: { BSTR version nullptr; HRESULT hr m_pDM-Ver(version); if (SUCCEEDED(hr) version) { resultTask.result QString::fromWCharArray(version); resultTask.success true; SysFreeString(version); } break; } case DMTaskType::FindWindow: { // 参数示例args[0]className, args[1]windowTitle if (task.args.size() 2) { QString className task.args[0].toString(); QString title task.args[1].toString(); long hwnd m_pDM-FindWindow( className.isEmpty() ? NULL : (LPCWSTR)className.utf16(), title.isEmpty() ? NULL : (LPCWSTR)title.utf16() ); resultTask.result (qint64)hwnd; resultTask.success (hwnd ! 0); } break; } case DMTaskType::Ocr: { // 参数示例args[0]x1, args[1]y1, args[2]x2, args[3]y2, args[4]colorFormat, args[5]sim if (task.args.size() 6) { long x1 task.args[0].toLongLong(); long y1 task.args[1].toLongLong(); long x2 task.args[2].toLongLong(); long y2 task.args[3].toLongLong(); QString colorFormat task.args[4].toString(); double sim task.args[5].toDouble(); BSTR ocrResult nullptr; HRESULT hr m_pDM-Ocr(x1, y1, x2, y2, (LPCWSTR)colorFormat.utf16(), sim, ocrResult); if (SUCCEEDED(hr) ocrResult) { resultTask.result QString::fromWCharArray(ocrResult); resultTask.success true; SysFreeString(ocrResult); } } break; } // ... 其他任务类型的实现 default: break; } return resultTask; }3. 在主线程或其他线程中调用// main_window.cpp 示例 #include main_window.h #include dm_worker.h #include QDebug MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 创建大漠工作线程 m_dmWorker new DMWorker(this); connect(m_dmWorker, DMWorker::taskFinished, this, MainWindow::onDMTaskFinished); connect(m_dmWorker, DMWorker::workerError, this, MainWindow::onDMError); m_dmWorker-start(); // 启动工作线程 // 示例提交一个获取版本号的任务 qint64 taskId m_dmWorker-submitTask(DMTaskType::GetVersion); m_taskMap[taskId] GetVersion; // 记录任务ID对应的操作 // 示例提交一个查找窗口的任务 QVectorQVariant findWindowArgs; findWindowArgs QString() QString(记事本); qint64 findTaskId m_dmWorker-submitTask(DMTaskType::FindWindow, findWindowArgs); m_taskMap[findTaskId] FindWindow; } void MainWindow::onDMTaskFinished(qint64 taskId, const QVariant result, bool success) { QString operation m_taskMap.value(taskId, Unknown); if (success) { qDebug() Task operation (ID: taskId ) finished. Result: result; if (operation FindWindow) { qint64 hwnd result.toLongLong(); if (hwnd) { // 找到窗口后可以继续提交绑定窗口等任务 QVectorQVariant bindArgs; bindArgs result QString(gdi) QString(windows) QString() 0; // m_dmWorker-submitTask(DMTaskType::BindWindow, bindArgs); } } } else { qDebug() Task operation (ID: taskId ) failed.; } m_taskMap.remove(taskId); } void MainWindow::onDMError(const QString error) { qCritical() DM Worker Error: error; } MainWindow::~MainWindow() { if (m_dmWorker) { m_dmWorker-quit(); m_dmWorker-wait(); delete m_dmWorker; } }三、关键注意事项与排错线程模型必须为STA每个创建和使用大漠对象的线程必须调用CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)进行初始化并在退出时调用CoUninitialize()。在上面的DMWorker::run()中已经体现。消息泵Message Pump是生命线持有大漠对象的线程必须运行消息循环QThread::exec()或QEventLoop否则某些需要消息派发的COM调用或大漠内部操作会挂起。DMWorker中的exec()调用确保了这一点。接口指针严禁跨线程直接使用绝对不能在A线程中获取的Idmsoft*指针直接在B线程中调用其方法。必须通过线程间通信如信号槽、事件将操作请求序列化到对象所属线程执行。对象生命周期管理大漠对象应在其所属线程中创建和销毁。在线程结束时必须确保释放所有接口指针Release()并卸载DLL如上文run()函数末尾所示。避免阻塞工作线程工作线程中的任务执行应尽量快速。耗时操作如大范围的Ocr或FindPic会阻塞任务队列。如果任务本身很耗时考虑将其拆分为多个小任务或使用多个DMWorker线程线程隔离方案。错误处理与超时在实际应用中应为任务执行增加超时机制。如果某个任务长时间未返回taskFinished信号调用方应能感知并做超时处理防止因某个操作卡死导致整个队列停滞。调试与日志在多线程环境下调试更加困难。建议在所有任务提交、开始、结束以及COM调用前后添加详细的日志输出便于追踪执行流程和定位死锁或崩溃问题。总结在Qt多线程中使用大漠插件最稳健的方式是设计一个专有工作线程配合任务队列。该线程负责COM库初始化、大漠对象生命周期管理以及所有接口调用并通过Qt信号槽机制与其他线程通信。这有效规避了COM线程安全问题同时保持了Qt事件驱动的优雅性。对于需要更高并发的场景可以实例化多个这样的工作线程每个线程管理一个独立的窗口或任务集实现线程级并行。参考来源VC调用大漠的方法大漠插件内存遍历技术实践指南大漠插件在网页数据抓取中的应用详解大漠插件中文版网页抓取与自动化实战指南大漠插件7.2213 Python注册实例详解大漠插件全面讲解与实战应用
返回列表