CMake+QT+大漠插件的桌面应用开发

发布时间:2024年01月13日

CMake+QT+大漠插件的桌面应用开发

说明

  • CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件中已经说明了如何免注册调用大漠插件,以及做了几个简单的功能调用(查找窗口、截图)
  • 下面来利用QT做一个简单的窗口查找、截图的桌面工具应用,功能点如下
    • 点击“注册”选项完成大漠插件的注册。
    • 用户在文本框输入窗口标题后,点击“查询”按钮,可对包含该标题的窗口进行查询。
    • 提供表格展示查询到的窗口信息。
    • 点击“截图”按钮,对选中的窗口进行截图并保存。
  • 界面如下

image.png

  • 目前主窗口的UI操作和大漠的调用是在一个线程里面的,当大漠调用时间过长时会出现UI界面卡顿的现象,下一篇将会给出如何处理这种问题的示例。

环境

版本/规范备注
平台win32操作系统为Windows10
CMake3.27.8CLion自带
C++17
ToolchainVisualStudio 2022只用其工具链,记得先安装好
QT5.12.12安装时选择msvc2017,不要64位的
DM7.2353
CLion2023.3.2你也可以用其他IDE工具
  • 启动IDE时,记得以管理员模式启动

项目结构

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_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})

代码

  • dmutil.h
#ifndef DM_DEMO_X_DMUTIL_H
#define DM_DEMO_X_DMUTIL_H

#include <string>
#include <vector>


// #import "./external/dm.dll" no_namespace
#include "./external/dm.tlh"

#define DM_LIB_PATH L"./external/dm.dll"

using namespace std;

struct MyWindow {
    long hwnd;
    wstring title;
    long processId;
};

/**
 * 注册dm.dll,获取大漠实例
 * @return 大漠实例
 */
Idmsoft *GetDmObject();

/**
 * 初始化大漠插件,并注册用户VIP
 * @return 大漠实例
 */
Idmsoft *initialDMAndRegVIP();

/**
 * 获取匹配的窗口
 * @param baseVec 保存window的容器
 * @param pDm 大漠插件
 * @param title 窗口标题中的字段(模糊匹配)
 * @param processName 进程名称(精确匹配,不区分大小写),默认不会根据进程名筛选窗口
 */
void getMatchedWindows(vector<MyWindow>& baseVec, Idmsoft *pDm, const wstring& title, const wstring& processName = L"");

#endif //DM_DEMO_X_DMUTIL_H

  • 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;
    }
    // 注册dm.dll成功,打印版本
    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;
    // long releaseRes = pDm->ReleaseRef();
    // cout << "===> ReleaseCode = " << releaseRes << 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, ",");

    // 装入容器vector
    baseVec.reserve(hwndStrVec.size());
    for (const string_view& element : hwndStrVec) {
        long curHwnd = viewToInt(element);
        // 获取窗口title
        const _bstr_t &curTitle = pDm->GetWindowTitle(curHwnd);
        // const _bstr_t &className = pDm->GetWindowClass(curHwnd);
        long processId = pDm->GetWindowProcessId(curHwnd);
        baseVec.push_back({curHwnd, {curTitle}, processId});
    }
    
}
  • strutils.h
#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更加高效
 * @param content 待分隔的字符串
 * @param delim 分隔符,默认为空格
 * @return vector容器
 */
vector<string_view> splitSV(string_view content, string_view delim = " ");

/**
 * string_view 转 int,失败时抛出异常
 * @param content 字符串内容view
 * @return int值
 */
int viewToInt(string_view content);

#endif //DM_DEMO_X_STRUTILS_H

  • strutils.cpp
#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;
}

  • mymainwindow.ui
<?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>

  • mymainwindow.h
#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 = "告警");

    /**
     * 注册大漠
     * @param pDm 大漠插件,待赋值
     */
    void doRegDM(Idmsoft **pDm);

    /**
     * 查询匹配的窗口
     * @param pDm 大漠插件
     * @param title 窗口标题(模糊查询)
     */
    void doFindWindow(Idmsoft *pDm, const QString &title);

    /**
     * 对窗口截图
     * @param pDm 大漠插件
     * @param hwnd 窗口句柄
     */
    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 //QT_DM_DEMO_X_MYMAINWINDOW_H
  • mymainwindow.cpp

// You may need to build the project (run Qt uic code generator) to get "ui_MyMainWindow.h" resolved

#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);

    // Init Views
    setFixedSize(1280, 720);

    ui->tableWidget->setColumnCount(3);
    ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "进程ID" << "句柄" << "标题");
    ui->tableWidget->horizontalHeader()->setStretchLastSection(true); // 最后一列自动铺满表格
    // ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
    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); // 选中整行

    // Init Listener
    // 注册大漠
    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;
    }

    // 找一下title包含findStr的窗口,并打印信息

    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();
}
  • main.cpp
#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();
}

文章来源:https://blog.csdn.net/alionsss/article/details/135573287
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。