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 ( ) ;
}