需求:在Windows平台软件中集成一个VR模块,能够在Qt软件中集成一个UE生成的可执行窗口程序。

  1. 新建一个c++类:VRModule,继承QWidget。这个类适是用于加载UE程序的。添加头文件:
#include <QObject>
#include <QWidget>
#include <QProcess>
#include <QTimer>
#include <QtConcurrent>  // 需要在Pro文件中添加QT += concurrent
#include "windows.h"
  1. 定义一个定时器,用于更新窗口的激活状态和位置
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &VRModule::timerShowUe);
m_timer->start(10);
void VRModule::timerShowUe() {
    if (m_Widget) {
        if (GetForegroundWindow() != m_hwnWindow && !this->isActiveWindow()) {
            SetWindowPos(m_hwnWindow,
                        HWND_NOTOPMOST,
                        mapToGlobal(m_Widget->pos()).x(),
                        mapToGlobal(m_Widget->pos()).y(),
                        m_Widget->width(),
                        m_Widget->height(),
                        SWP_NOACTIVATE);
        } else {
            SetWindowPos(m_hwnWindow,
                         HWND_TOPMOST,
                         mapToGlobal(m_Widget->pos()).x(),
                         mapToGlobal(m_Widget->pos()).y(),
                         m_Widget->width(),
                         m_Widget->height(),
                         SWP_NOACTIVATE);
        }
    }
}
  • GetForegroundWindow() 函数用于获取当前前台窗口的句柄。
  • m_hwnWindow ue程序窗口句柄
  • this->isActiveWindow() 检查当前窗口是否处于活动状态。
  • SetWindowPos() 是一个 Windows API 函数,用于改变窗口的位置和大小。

这段代码的功能解释如下:

  • 如果前台窗口不是 m_hwnWindow,且当前窗口不处于活动状态,代码使用 SetWindowPos() 设置 m_hwnWindow 的位置,使其不是最顶层的窗口。
  • 如果不满足上述条件,则使用 SetWindowPos()m_hwnWindow 设置为最顶层的窗口。

mapToGlobal() 函数用于将显示UE程序的QWidget坐标转换为屏幕坐标。SWP_NOACTIVATE 标志用于在移动窗口时防止激活窗口。

  1. 添加一个函数,用于外部调用该类,传递UE程序的路径和用于显示UE程序的QWidget窗口
void VRModule::getPathAndWidgetContainer(const QString &path, QWidget *widgetContainer) {
    m_Widget = widgetContainer;
    //获取启动程序的名字
    QString fileNameWithExtension = QFileInfo(path).fileName();
    QString baseName = fileNameWithExtension.section('.', 0, 0);
    QString APPName = QString("%1 (64-bit Development PCD3D_SM6) ").arg(baseName);
    m_appName = APPName.toStdWString();

    //应用程序以窗口模式运行,而不是全屏
    QStringList arguments;
    arguments << "-WINDOWED";

    m_process = new QProcess;
    m_process->start(path, arguments);
    QtConcurrent::run([this]{
        while (true) {
            m_hwnWindow = FindWindow(L"UnrealWindow", m_appName.c_str());
            LONG style = GetWindowLong(m_hwnWindow, GWL_EXSTYLE) & (~WS_OVERLAPPEDWINDOW);
            //隐藏图标
            SetWindowLong(m_hwnWindow, GWL_EXSTYLE, style | WS_EX_TOOLWINDOW);
            //隐藏菜单栏
            SetWindowLong(m_hwnWindow, GWL_STYLE, GetWindowLong(m_hwnWindow, GWL_STYLE) & (~WS_OVERLAPPEDWINDOW));
            if (m_hwnWindow != NULL) {
                emit signal_ueComplete();
                break;
            }
        }
    });
}
  • 接收一个UE程序文件路径 path 和一个 QWidget 指针 widgetContainer
  • 从文件路径中提取UE程序文件名,并构造UE应用程序名字。
  • 将UE应用程序以窗口模式启动,而不是全屏模式,使用 QProcess 对象启动应用程序。
  • 使用 QtConcurrent::run() 启动一个新的线程,在这个线程中,不断查找指定名字的窗口(UnrealWindow),直到找到为止。
  • 当找到窗口后,隐藏窗口的图标和菜单栏,然后发出一个信号 signal_ueComplete()。
void VRModule::slot_ueComplete() {
    MoveWindow(m_hwnWindow,
               mapToGlobal(m_Widget->pos()).x(),
               mapToGlobal(m_Widget->pos()).y(),
               m_Widget->width(),
               m_Widget->height(),
               false);
}

找到UE启动程序后触发槽函数,移动UE程序到外部接口提供的QWidget窗口中,并将UE程序的窗口大小设置为QWidget窗口的大小。最后一个false表示窗口不需要重绘。

  1. 重新窗口显示、隐藏、大小改变的事件。当QWidget显示、隐藏、大小变化时,UE程序的窗口也随之变化。

void VRModule::showEvent(QShowEvent *event) {
    Q_UNUSED(event);
    if (m_process) {
        ShowWindow(m_hwnWindow, SW_SHOWNOACTIVATE);
    }
}

void VRModule::resizeEvent(QResizeEvent *event) {
    Q_UNUSED(event);
    if (m_Widget) {
        MoveWindow(m_hwnWindow,
                   mapToGlobal(m_Widget->pos()).x(),
                   mapToGlobal(m_Widget->pos()).y(),
                   m_Widget->width(),
                   m_Widget->height(),
                   false);
    }
}

void VRModule::hideEvent(QHideEvent *event) {
    Q_UNUSED(event);
    if (m_process) {
        ShowWindow(m_hwnWindow, SW_HIDE);
    }
}
  1. 析构函数,当Qt窗口关闭时,需要将后台运行的UnrealGame应用程序杀掉,这个应用程序占很大的内存,而且不杀掉的话下一次启动会有问题。
VRModule::~VRModule() {
    killProcessByName("UnrealGame.exe");
    delete m_Widget;
    delete m_timer;
    if (m_process) {
        // 给进程发送退出信号
        m_process->terminate();
        if (!m_process->waitForFinished(5000)) {
            // 如果进程在规定时间内未能退出,强制结束进程
            m_process->kill();
        }
        delete m_process;
    }
}

void VRModule::killProcessByName(const QString &processName) {
    QProcess process;
    // 使用tasklist命令获取所有运行的进程列表
    process.start("tasklist", QStringList() << "/fo" << "csv" << "/nh");
    process.waitForFinished();
    QString output = process.readAllStandardOutput();

    // 查找所有的进程及其PID
    QStringList lines = output.split("\n");
    for (QString &line : lines) {
        QStringList fields = line.split(",");
        if (fields.count() < 2) {
            continue;
        }
        QString pid = fields[1].trimmed().replace("\"", "");
        QString name = fields[0].trimmed().replace("\"", "");

        if (name == processName) {
            QProcess::execute("taskkill", QStringList() << "/F" << "/PID" << pid);
        }
    }
}

  • 创建了一个 QProcess 对象 process 用于执行系统命令。
  • 使用 process.start() 启动系统命令 “tasklist”,并传递参数 “/fo”、“csv” 和 “/nh”。这些参数指示 tasklist 命令以 CSV 格式输出,且不包括标题。
  • 使用 process.waitForFinished() 等待进程执行完成。
  • 使用 process.readAllStandardOutput() 读取命令的标准输出,即运行中的进程列表。
  • 将输出的文本按行拆分,并逐行处理。
  • 对于每一行,将其按逗号分隔,提取进程名称和进程ID。
  • 如果进程名称与指定的 processName 匹配,则使用 taskkill 命令终止该进程。这里使用了 QProcess::execute() 函数执行系统命令 “taskkill”,并传递参数 “/F”(强制终止进程)和 “/PID”,后接要终止的进程ID。

总之,这段代码通过执行系统命令来列出所有运行中的进程,然后根据进程名称匹配来终止指定的进程。

  1. 再新建一个Qt的QWidget窗口程序VRWidget,用于显示UE程序界面窗口。调用VRModule类中的getPathAndWidgetContainer方法。
void VRWidget::loadVR() {
    QString path = "..\\Windows\\qt_ue.exe";
    vrModule->getPathAndWidgetContainer(path, ui->widget);
}

效果:
20240201_110956 00_00_00-00_00_30.gif

#ifndef VRMODULE_H
#define VRMODULE_H

#include <QObject>
#include <QWidget>
#include <QProcess>
#include <QTimer>
#include <QtConcurrent>
#include "windows.h"

///
/// \brief The VRModule class   VR模组,用于加载VR程序
///
class VRModule : public QWidget
{
    Q_OBJECT
public:
    explicit VRModule(QWidget *parent = nullptr);

    ~VRModule();

public:
    /// 获取VR路径并返回显示窗口的布局
    void getPathAndWidgetContainer(const QString &path, QWidget *widgetContainer);

protected:
    void resizeEvent(QResizeEvent *event) override;

    void showEvent(QShowEvent *event) override;

    void hideEvent(QHideEvent *event) override;

private:
    /// 子进程:启动ue程序
    QProcess *m_process{Q_NULLPTR};
    /// ue程序窗口句柄
    HWND m_hwnWindow{Q_NULLPTR};

    QWidget *m_Widget{Q_NULLPTR};
    /// 定时器:更新界面位置、大小等
    QTimer *m_timer{Q_NULLPTR};
    /// 应用程序名称
    std::wstring m_appName;

private:
    /// 初始化界面
    void initWidget();

    /// 定时器刷新UE窗口
    void timerShowUe();

    ///通过进程名字kill该进程
    void killProcessByName(const QString &processName);

signals:
    /// 信号:完成ue程序启动
    void signal_ueComplete();

private slots:
    /// 控制程序显示位置和大小
    void slot_ueComplete();

};

#endif // VRMODULE_H

#include "vrmodule.h"

VRModule::VRModule(QWidget *parent) : QWidget(parent) {
    initWidget();
}

VRModule::~VRModule() {
    killProcessByName("UnrealGame.exe");
    delete m_Widget;
    delete m_timer;
    if (m_process) {
        // 给进程发送退出信号
        m_process->terminate();
        if (!m_process->waitForFinished(5000)) {
            // 如果进程在规定时间内未能退出,强制结束进程
            m_process->kill();
        }
        delete m_process;
    }
}

void VRModule::initWidget() {
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, &VRModule::timerShowUe);
    m_timer->start(10);

    connect(this, &VRModule::signal_ueComplete, this, &VRModule::slot_ueComplete);
}

void VRModule::timerShowUe() {
    if (m_Widget) {
        if (GetForegroundWindow() != m_hwnWindow && !this->isActiveWindow()) {
            SetWindowPos(m_hwnWindow,
                        HWND_NOTOPMOST,
                        mapToGlobal(m_Widget->pos()).x(),
                        mapToGlobal(m_Widget->pos()).y(),
                        m_Widget->width(),
                        m_Widget->height(),
                        SWP_NOACTIVATE);
        } else {
            SetWindowPos(m_hwnWindow,
                         HWND_TOPMOST,
                         mapToGlobal(m_Widget->pos()).x(),
                         mapToGlobal(m_Widget->pos()).y(),
                         m_Widget->width(),
                         m_Widget->height(),
                         SWP_NOACTIVATE);
        }
    }
}

void VRModule::getPathAndWidgetContainer(const QString &path, QWidget *widgetContainer) {
    m_Widget = widgetContainer;
    //获取启动程序的名字
    QString fileNameWithExtension = QFileInfo(path).fileName();
    QString baseName = fileNameWithExtension.section('.', 0, 0);
    QString APPName = QString("%1 (64-bit Development PCD3D_SM6) ").arg(baseName);
    m_appName = APPName.toStdWString();

    //应用程序以窗口模式运行,而不是全屏
    QStringList arguments;
    arguments << "-WINDOWED";

    m_process = new QProcess;
    m_process->start(path, arguments);
    QtConcurrent::run([this]{
        while (true) {
            m_hwnWindow = FindWindow(L"UnrealWindow", m_appName.c_str());
            LONG style = GetWindowLong(m_hwnWindow, GWL_EXSTYLE) & (~WS_OVERLAPPEDWINDOW);
            //隐藏图标
            SetWindowLong(m_hwnWindow, GWL_EXSTYLE, style | WS_EX_TOOLWINDOW);
            //隐藏菜单栏
            SetWindowLong(m_hwnWindow, GWL_STYLE, GetWindowLong(m_hwnWindow, GWL_STYLE) & (~WS_OVERLAPPEDWINDOW));
            if (m_hwnWindow != NULL) {
                emit signal_ueComplete();
                break;
            }
        }
    });
}

void VRModule::slot_ueComplete() {
    MoveWindow(m_hwnWindow,
               mapToGlobal(m_Widget->pos()).x(),
               mapToGlobal(m_Widget->pos()).y(),
               m_Widget->width(),
               m_Widget->height(),
               false);
}

void VRModule::killProcessByName(const QString &processName) {
    QProcess process;
    // 使用tasklist命令获取所有运行的进程列表
    process.start("tasklist", QStringList() << "/fo" << "csv" << "/nh");
    process.waitForFinished();
    QString output = process.readAllStandardOutput();

    // 查找所有的进程及其PID
    QStringList lines = output.split("\n");
    for (QString &line : lines) {
        QStringList fields = line.split(",");
        if (fields.count() < 2) {
            continue;
        }
        QString pid = fields[1].trimmed().replace("\"", "");
        QString name = fields[0].trimmed().replace("\"", "");

        if (name == processName) {
            QProcess::execute("taskkill", QStringList() << "/F" << "/PID" << pid);
        }
    }
}

void VRModule::showEvent(QShowEvent *event) {
    Q_UNUSED(event);
    if (m_process) {
        ShowWindow(m_hwnWindow, SW_SHOWNOACTIVATE);
    }
}

void VRModule::resizeEvent(QResizeEvent *event) {
    Q_UNUSED(event);
    if (m_Widget) {
        MoveWindow(m_hwnWindow,
                   mapToGlobal(m_Widget->pos()).x(),
                   mapToGlobal(m_Widget->pos()).y(),
                   m_Widget->width(),
                   m_Widget->height(),
                   false);
    }
}

void VRModule::hideEvent(QHideEvent *event) {
    Q_UNUSED(event);
    if (m_process) {
        ShowWindow(m_hwnWindow, SW_HIDE);
    }
}

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐