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
"$/plugins/platforms/")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
"$/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"
"$")
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
#include
#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
#include
#include
#include
#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
#include
#include
#include
using namespace std;
vector<string_view> splitSV(string_view content, string_view delim = " ");
int viewToInt(string_view content);
#endif
#include
#include
#include
#include
#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;
}
<ui version="4.0">
<class>MyMainWindowclass>
<widget class="QMainWindow" name="MyMainWindow">
<property name="geometry">
<rect>
<x>0x>
<y>0y>
<width>400width>
<height>300height>
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::Horizontalenum>
property>
<property name="sizeHint" stdset="0">
<size>
<width>40width>
<height>20height>
size>
property>
spacer>
item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75weight>
<bold>truebold>
font>
property>
<property name="text">
<string>窗口标题:string>
property>
widget>
item>
<item>
<widget class="QLineEdit" name="edtTitle">
<property name="minimumSize">
<size>
<width>200width>
<height>0height>
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::Horizontalenum>
property>
<property name="sizeHint" stdset="0">
<size>
<width>40width>
<height>20height>
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::Horizontalenum>
property>
<property name="sizeHint" stdset="0">
<size>
<width>40width>
<height>20height>
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>0x>
<y>0y>
<width>400width>
<height>21height>
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>注册DMstring>
property>
action>
widget>
<resources/>
<connections/>
ui>
#ifndef QT_DM_DEMO_X_MYMAINWINDOW_H
#define QT_DM_DEMO_X_MYMAINWINDOW_H
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 ==========" << endl;
showMessageBox(false, "DM 注册失败!");
return;
}
cout << "========== Initial DM ==========" << 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
#include
#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();
}