需求:在Windows平台软件中集成一个VR模块,能够在Qt软件中集成一个UE生成的可执行窗口程序。
- 新建一个c++类:VRModule,继承QWidget。这个类适是用于加载UE程序的。添加头文件:
#include <QObject>
#include <QWidget>
#include <QProcess>
#include <QTimer>
#include <QtConcurrent> // 需要在Pro文件中添加QT += concurrent
#include "windows.h"
- 定义一个定时器,用于更新窗口的激活状态和位置
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 标志用于在移动窗口时防止激活窗口。
- 添加一个函数,用于外部调用该类,传递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表示窗口不需要重绘。
- 重新窗口显示、隐藏、大小改变的事件。当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);
}
}
- 析构函数,当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。
总之,这段代码通过执行系统命令来列出所有运行中的进程,然后根据进程名称匹配来终止指定的进程。
- 再新建一个Qt的QWidget窗口程序VRWidget,用于显示UE程序界面窗口。调用VRModule类中的getPathAndWidgetContainer方法。
void VRWidget::loadVR() {
QString path = "..\\Windows\\qt_ue.exe";
vrModule->getPathAndWidgetContainer(path, ui->widget);
}
效果:
#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);
}
}