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)
set(CMAKE_PREFIX_PATH "C:/Qt/Qt5.12.12/5.12.12/msvc2017")
find_package(Qt5 COMPONENTS
Core
Gui
Widgets
REQUIRED)
add_executable(${PROJECT_NAME} main.cpp
strutils.cpp strutils.h
dmutil.cpp dmutil.h
mymainwindow.cpp mymainwindow.h mymainwindow.ui
)
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Gui
Qt5::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_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${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_BUILD
COMMAND ${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_NAMESPACE
class 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();
}