CMake+QT+大漠插件的桌面应用开发
简介
- 在CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件中已经说明了如何免注册调用大漠插件,以及做了几个简单的功能调用(查找窗口、截图)
- 下面来利用QT做一个简单的窗口查找、截图的桌面工具应用,功能点如下
- 点击“注册”选项完成大漠插件的注册。
- 用户在文本框输入窗口标题后,点击“查询”按钮,可对包含该标题的窗口进行查询。
- 提供表格展示查询到的窗口信息。
- 点击“截图”按钮,对选中的窗口进行截图并保存。
- 界面如下
- 目前主窗口的UI操作和大漠的调用是在一个线程里面的,当大漠调用时间过长时会出现UI界面卡顿的现象,下一篇将会给出如何处理这种问题的示例。
环境
| 版本/规范 | 备注 |
---|
平台 | win32 | 操作系统为Windows10 |
CMake | 3.27.8 | CLion自带 |
C++ | 17 | |
Toolchain | VisualStudio 2022 | 只用其工具链,记得先安装好 |
QT | 5.12.12 | 安装时选择msvc2017,不要64位的 |
DM | 7.2353 | |
CLion | 2023.3.2 | 你也可以用其他IDE工具 |
项目结构
- 新建一个项目 qt_dm_demo_x_01
- 将下载好的 dm.dll 文件以及处理好的 dm.tlh、dm.tli 文件放置到项目的 external 目录下
- 注:dm.tlh、dm.tli 文件的生成请参考 CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件
qt_dm_demo_x_01 # 项目目录
--|cmake-build-debug-visual-studio # 工程构建目录,存临时生成的文件
--|--|...
--|external # 引入第三方库文件的所在的文件夹
--|--|dm.dll # 大漠插件的dll
--|--|dm.tlh
--|--|dm.tli
--CMakeLists.txt # CMake脚本文件
--dmutil.cpp # 大漠的功能封装工具
--dmutil.h # 大漠的功能封装工具
--main.cpp # 程序入口
--mymainwindow.cpp # 主窗口
--mymainwindow.h # 主窗口
--mymainwindow.ui # 主窗口的UI文件
--strutils.cpp # 字符串工具
--strutils.h # 字符串工具
配置编译环境
- 配置工具链
- 和CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件中保持一致即可
- CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.27)
project(qt_dm_demo_x_01)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)# QT安装的msvc地址
set(CMAKE_PREFIX_PATH "C:/Qt/Qt5.12.12/5.12.12/msvc2017")
# 查找QT组件包
find_package(Qt5 COMPONENTSCoreGuiWidgetsREQUIRED)
# 生成可执行文件
add_executable(${PROJECT_NAME} main.cppstrutils.cpp strutils.hdmutil.cpp dmutil.hmymainwindow.cpp mymainwindow.h mymainwindow.ui
)
# 链接需要的QT库
target_link_libraries(${PROJECT_NAME}Qt5::CoreQt5::GuiQt5::Widgets
)target_compile_definitions(${PROJECT_NAME} PRIVATE-DWIN32# -D_DEBUG-D_WINDOWS-D_UNICODE-DUNICODE
)message(STATUS "CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
# 拷贝库文件到生成的可执行文件旁边
if (WIN32 AND NOT DEFINED CMAKETOOLCHAIN_FILE)set(DEBUG_SUFFIX)if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")set(DEBUG_SUFFIX "d")endif ()set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")if (NOT EXISTS "${QT_INSTALL_PATH}/bin")set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")if (NOT EXISTS "${QT_INSTALL_PATH}/bin")set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")endif ()endif ()if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E make_directory"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll""$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")endif ()foreach (QT_LIB Core Gui Widgets)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy"${QT_INSTALL_PATH}/bin/Qt5${QT_LIB}${DEBUG_SUFFIX}.dll""$<TARGET_FILE_DIR:${PROJECT_NAME}>")endforeach (QT_LIB)
endif ()# 拷贝资源文件 dm.dll
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/external DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
代码
#ifndef DM_DEMO_X_DMUTIL_H
#define DM_DEMO_X_DMUTIL_H#include <string>
#include <vector>
#include "./external/dm.tlh"#define DM_LIB_PATH L"./external/dm.dll"using namespace std;struct MyWindow {long hwnd;wstring title;long processId;
};
Idmsoft *GetDmObject();
Idmsoft *initialDMAndRegVIP();
void getMatchedWindows(vector<MyWindow>& baseVec, Idmsoft *pDm, const wstring& title, const wstring& processName = L"");#endif
- dmutil.cpp(记得填入自己的 注册码 和 附加码)
#include <iostream>
#include <sstream>
#include <string_view>
#include <vector>
#include "dmutil.h"
#include "strutils.h"using namespace std;Idmsoft *GetDmObject() {Idmsoft *m_dm = nullptr;bool m_bInit = false;typedef HRESULT(_stdcall*pfnGCO)(REFCLSID, REFIID, void**);pfnGCO fnGCO = nullptr;HINSTANCE hdllInst = LoadLibrary(DM_LIB_PATH);if (hdllInst == nullptr) {cout << "Load library 'dm.dll' failed ! DM_LIB_PATH = " << DM_LIB_PATH << endl;return nullptr;}fnGCO = (pfnGCO) GetProcAddress(hdllInst, "DllGetClassObject");if (fnGCO != nullptr) {IClassFactory *pcf = nullptr;HRESULT hr = (fnGCO)(__uuidof(dmsoft), IID_IClassFactory, (void **) &pcf);if (SUCCEEDED(hr) && (pcf != nullptr)) {hr = pcf->CreateInstance(nullptr, __uuidof(Idmsoft), (void **) &m_dm);if ((SUCCEEDED(hr) && (m_dm != nullptr)) == FALSE) {cout << "Create instance 'Idmsoft' failed !" << endl;return nullptr;}}pcf->Release();m_bInit = true;}return m_dm;
}Idmsoft *initialDMAndRegVIP() {Idmsoft *pDm = GetDmObject();if (pDm == nullptr) {cout << "===> dm.dll registration failed !" << endl;return nullptr;}cout << "===> DM version: " << (char *) pDm->Ver() << endl;long regResult = pDm->Reg(L"注册码", L"版本附加信息(附加码)");if (regResult != 1) {cout << "===> Account registration failed ! code = " << regResult << endl;return nullptr;}cout << "===> Account registration successful ! " << endl;return pDm;
}void getMatchedWindows(vector<MyWindow>& baseVec, Idmsoft *pDm, const wstring& title, const wstring& processName) {_bstr_t hwnds;if (!processName.empty()) {hwnds = pDm->EnumWindowByProcess(processName.c_str(), title.c_str(), L"", 1 + 8 + 16);} else {hwnds = pDm->EnumWindow(0, title.c_str(), L"", 1 + 4 + 8 + 16);}string content(hwnds);vector<string_view> hwndStrVec = splitSV(content, ",");baseVec.reserve(hwndStrVec.size());for (const string_view& element : hwndStrVec) {long curHwnd = viewToInt(element);const _bstr_t &curTitle = pDm->GetWindowTitle(curHwnd);long processId = pDm->GetWindowProcessId(curHwnd);baseVec.push_back({curHwnd, {curTitle}, processId});}}
#ifndef DM_DEMO_X_STRUTILS_H
#define DM_DEMO_X_STRUTILS_H#include <string>
#include <string_view>
#include <iostream>
#include <vector>using namespace std;
vector<string_view> splitSV(string_view content, string_view delim = " ");
int viewToInt(string_view content);#endif
#include <sstream>
#include <string>
#include <vector>
#include <charconv>#include "strutils.h"vector<string_view> splitSV(string_view content, string_view delim) {vector<string_view> output;size_t first = 0;while (first < content.size()) {const auto second = content.find_first_of(delim, first);if (first != second)output.emplace_back(content.substr(first, second - first));if (second == string_view::npos)break;first = second + 1;}return output;
}int viewToInt(string_view content) {int num;auto result = std::from_chars(content.data(), content.data() + content.size(), num);if (result.ec == std::errc::invalid_argument) {throw std::runtime_error("Could not convert.");}return num;
}
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"><class>MyMainWindow</class><widget class="QMainWindow" name="MyMainWindow"><property name="geometry"><rect><x>0</x><y>0</y><width>400</width><height>300</height></rect></property><property name="windowTitle"><string>窗口查询程序</string></property><widget class="QWidget" name="centralwidget"><layout class="QVBoxLayout" name="verticalLayout"><item><layout class="QHBoxLayout" name="horizontalLayout"><item><spacer name="horizontalSpacer"><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item><item><widget class="QLabel" name="label"><property name="font"><font><weight>75</weight><bold>true</bold></font></property><property name="text"><string>窗口标题:</string></property></widget></item><item><widget class="QLineEdit" name="edtTitle"><property name="minimumSize"><size><width>200</width><height>0</height></size></property></widget></item><item><widget class="QPushButton" name="btnQuery"><property name="text"><string>模糊查询</string></property></widget></item><item><spacer name="horizontalSpacer_3"><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item><item><widget class="QPushButton" name="btnCapture"><property name="text"><string>截图(选中行)</string></property></widget></item><item><spacer name="horizontalSpacer_2"><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item></layout></item><item><widget class="QTableWidget" name="tableWidget"/></item></layout></widget><widget class="QMenuBar" name="menubar"><property name="geometry"><rect><x>0</x><y>0</y><width>400</width><height>21</height></rect></property><widget class="QMenu" name="menuOperation"><property name="title"><string>菜单</string></property><addaction name="actionReg"/></widget><addaction name="menuOperation"/></widget><widget class="QStatusBar" name="statusbar"/><action name="actionReg"><property name="text"><string>注册DM</string></property></action></widget><resources/><connections/>
</ui>
#ifndef QT_DM_DEMO_X_MYMAINWINDOW_H
#define QT_DM_DEMO_X_MYMAINWINDOW_H#include <QMainWindow>#include "dmutil.h"QT_BEGIN_NAMESPACE
namespace Ui { class MyMainWindow; }
QT_END_NAMESPACEclass MyMainWindow : public QMainWindow {
Q_OBJECT
public:explicit MyMainWindow(QWidget *parent = nullptr);~MyMainWindow() override;public:void showInfo(const QString &message, const QString &title = "提示");void showWarn(const QString &message, const QString &title = "告警");void doRegDM(Idmsoft **pDm);void doFindWindow(Idmsoft *pDm, const QString &title);void doCaptureWindow(Idmsoft *pDm, long hwnd);public slots:void showMessageBox(bool result, const QString &message);void showTableView(bool result, const QString &msg, const vector<MyWindow> &windowVec);private:Ui::MyMainWindow *ui;Idmsoft *pCommonDm = nullptr;
};#endif
#include <QFont>
#include <QHeaderView>
#include <QMessageBox>
#include <QPushButton>
#include <QAction>
#include <QString>
#include <QTableWidgetItem>
#include <QObject>
#include <QVector>
#include <iostream>
#include "mymainwindow.h"
#include "ui_MyMainWindow.h"using namespace std;MyMainWindow::MyMainWindow(QWidget *parent) :QMainWindow(parent), ui(new Ui::MyMainWindow) {ui->setupUi(this);setFixedSize(1280, 720);ui->tableWidget->setColumnCount(3);ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "进程ID" << "句柄" << "标题");ui->tableWidget->horizontalHeader()->setStretchLastSection(true); ui->tableWidget->horizontalHeader()->setHighlightSections(false);ui->tableWidget->horizontalHeader()->setStyleSheet("QHeaderView::section{background:gray;}");ui->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);QFont font = ui->tableWidget->horizontalHeader()->font();font.setBold(true);ui->tableWidget->horizontalHeader()->setFont(font);ui->tableWidget->setStyleSheet("QTableWidget::item:hover { background-color: lightblue; }");ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); connect(ui->actionReg, &QAction::triggered, [this]() {ui->actionReg->setEnabled(false);this->doRegDM(&this->pCommonDm);ui->actionReg->setEnabled(true);});connect(ui->btnQuery, &QPushButton::clicked, [this]() {ui->btnQuery->setEnabled(false);this->doFindWindow(this->pCommonDm, ui->edtTitle->text());ui->btnQuery->setEnabled(true);});connect(ui->btnCapture, &QPushButton::clicked, [this]() {ui->btnCapture->setEnabled(false);const QList<QTableWidgetItem *> &selectedItems = ui->tableWidget->selectedItems();if (selectedItems.size() >= 2) {QTableWidgetItem *item = selectedItems.at(1);const QString &hwnd = item->data(Qt::DisplayRole).toString();bool res = false;long hwndL = hwnd.toLong(&res, 0);cout << res << endl;if (res) {this->doCaptureWindow(this->pCommonDm, hwndL);} else {this->showWarn("选中行的窗口句柄解析异常!");}} else {this->showWarn("请选中列表中的其中一行!");}ui->btnCapture->setEnabled(true);});}MyMainWindow::~MyMainWindow() {delete ui;
}void MyMainWindow::showInfo(const QString &message, const QString &title) {QMessageBox::information(this, title, message);
}void MyMainWindow::showWarn(const QString &message, const QString &title) {QMessageBox::critical(this, title, message);
}void MyMainWindow::showMessageBox(const bool result, const QString& message) {if (result) {this->showInfo(message);} else {this->showWarn(message);}
}void MyMainWindow::showTableView(bool result, const QString &msg, const vector<MyWindow> &windowVec) {if (result) {auto rowNum = windowVec.size();ui->tableWidget->setRowCount(rowNum);for (int i = 0; i < rowNum; ++i) {const MyWindow &item = windowVec[i];ui->tableWidget->setItem(i, 0, new QTableWidgetItem(QString::number(item.processId)));ui->tableWidget->setItem(i, 1, new QTableWidgetItem(QString::number(item.hwnd)));ui->tableWidget->setItem(i, 2, new QTableWidgetItem(QString::fromStdWString(item.title)));}} else {this->showWarn(msg);}
}void MyMainWindow::doRegDM(Idmsoft **pDm) {cout << "========== Initial DM ............ ==========" << endl;*pDm = initialDMAndRegVIP();if (*pDm == nullptr) {cout << "========== Initial DM <Failed> ==========" << endl;showMessageBox(false, "DM 注册失败!");return;}cout << "========== Initial DM <Successful> ==========" << endl;cout << endl;showMessageBox(true, "DM 注册完成!");
}void MyMainWindow::doFindWindow(Idmsoft *pDm, const QString &title) {vector<MyWindow> windowVec;if (pDm == nullptr) {cout << "this->pCommonDm == nullptr" << endl;this->showTableView(false, "请先在菜单中完成注册!", windowVec);return;}getMatchedWindows(windowVec, pDm, title.toStdWString());if (windowVec.empty()) {cout << "can not find such window" << endl;this->showTableView(false, "没有找到包含该标题的窗口!", windowVec);return;}this->showTableView(true, "成功!", windowVec);
}void MyMainWindow::doCaptureWindow(Idmsoft *pDm, long hwnd) {if (pDm == nullptr) {cout << "this->pCommonDm == nullptr" << endl;this->showMessageBox(false, "请先在菜单中完成注册!");return;}long dmBind = pDm->BindWindowEx(hwnd,"normal","normal","normal","",0);if (dmBind == 1) {pDm->SetWindowState(hwnd, 12);pDm->SetWindowState(hwnd, 8);pDm->delay(600);wstring filename = wstring(L"./capture_window_").append(std::to_wstring(hwnd)).append(L".bmp");long retCap = pDm->Capture(0, 0, 2000, 2000, filename.c_str());if (retCap != 1) {cout << "capture failed" << endl;this->showMessageBox(false, "截图失败!");} else {cout << "capture success" << endl;this->showMessageBox(true, QString::fromStdWString(L"截图成功,保存地址为: " + filename));}pDm->SetWindowState(hwnd, 9);} else {cout << "DM BindWindow failed" << endl;this->showMessageBox(false, "绑定窗口异常!");}pDm->UnBindWindow();
}
#include <QApplication>
#include <iostream>
#include "mymainwindow.h"
using namespace std;int main(int argc, char *argv[]) {setlocale(LC_ALL, "chs");QApplication a(argc, argv);MyMainWindow mainWindow;mainWindow.show();return QApplication::exec();
}