关键字:GPT4QProcessFindWindowExQt嵌入

前言

今天计划是稍微研究一下Qt的多进程编程,因为最近的程序感觉有点慢,还有一个严重的问题就是会触发Windows的GDI限制,所以计划把程序分成几个进程来搞,变相的提高程序的性能多多几个GDI。
因为之前是搞过嵌入程序的,不过哪个是用的别人提供的SDK库,我只需要给他一个句柄来接他的UI即可,所以没有深入了解,今天需要我自己搭建一个框架出来。

调用外部程序

首先,如果想把程序嵌入进来,那我们就得先把程序启动了,不然就加载个空气进来了,所以这里先研究一下怎么启动一个进程。因为项目式理线开发,所以这里咱直接用GPT生成一段代码;

#include <QProcess>
#include <QDebug>

// ...

QProcess *process = new QProcess(this); // this 指定父对象,以便自动回收资源
QString program = "外部程序的路径"; // 比如 "notepad.exe"

// 启动外部程序
process->start(program);

// 等待程序启动完成或超时
if (!process->waitForStarted()) {
    qDebug() << "外部程序启动失败:" << process->errorString();
    return;
}

// 发送数据到外部程序的标准输入(如果需要)
// process->write("一些输入数据\n");

// 等待程序执行完成或超时
if (!process->waitForFinished()) {
    qDebug() << "外部程序执行失败:" << process->errorString();
    return;
}

// 读取外部程序的标准输出和标准错误输出
QString standardOutput = process->readAllStandardOutput();
QString standardError = process->readAllStandardError();

delete process; // 如果没有指定父对象,记得手动删除以避免内存泄漏

// 打印输出结果
qDebug() << "标准输出:" << standardOutput;
qDebug() << "标准错误:" << standardError;

关于 QProcess

QProcess 类是属于 Qt 框架中的一个类,用于启动和管理外部程序的执行。它允许你的 Qt 应用程序来控制其他程序,并且与它们通过标准输入输出进行交互,这非常有用于那些需要从其他应用程序接收数据或者向其发送数据的情况。

以下是 QProcess 类的一些基本功能:

  1. 启动外部程序:你可以指定程序名称和所需的参数来启动一个程序。
  2. 读取和写入数据:你可以通过程序的标准输入、输出和错误输出流来与之交互。这意味着你可以发送命令给进程或读取其输出结果。
  3. 同步或异步操作QProcess 可以同步运行(阻塞当前线程直到外部程序完成执行),或者异步运行(在独立的线程中执行,不干扰当前线程的执行流程)。
  4. 信号和槽机制:作为一个 QObject 的子类,QProcess 使用 Qt 的信号和槽机制。例如,你可以连接到 finished 信号以获取当外部程序完成时的通知,或者连接到 readyRead 信号来读取可用的数据。
  5. 处理程序输出QProcess 提供了多种方法来读取程序的标准输出和错误输出,包括readAllStandardOutputreadAllStandardError
  6. 错误处理QProcess 也能提供关于启动或运行过程中发生错误的信息。

下面是一个简单的例子,展示了如何使用 QProcess 来运行一个外部的 ping 命令:

#include <QProcess>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QProcess pingProcess;
    pingProcess.start("ping", QStringList() << "-c 4" << "www.example.com");

    QObject::connect(&pingProcess, &QProcess::finished, [&](int exitCode) {
        qDebug() << "Process finished with exit code" << exitCode;
        app.quit();
    });

    QObject::connect(&pingProcess, &QProcess::readyReadStandardOutput, [&]() {
        qDebug() << pingProcess.readAllStandardOutput();
    });

    QObject::connect(&pingProcess, &QProcess::readyReadStandardError, [&]() {
        qDebug() << pingProcess.readAllStandardError();
    });
    
    return app.exec();
}

在这个例子中,QProcess 用来运行 ping 命令并打印出结果。当 ping 命令结束后,程序也随之退出。这个程序还演示了如何连接信号到一个 lambda 表达式,这允许你直接在信号槽连接中写入处理代码。

嵌入到主程序显示

这里就要说一下GPT了,开始用的GPT3 后面换成了GPT4 ,还是一样,一下午都开始怀疑这路子是否可以行的通了,最终还是一步一步Debug发现问题。我们看下 GPT 的答案:
在Qt中,如果你想嵌入一个外部程序的窗口到你的Qt应用程序中,你需要获取外部程序窗口的句柄(HWND在Windows中)并且将其设置为你的Qt窗口的子窗口。这通常涉及到Windows API的调用,因此这种方法是平台依赖的。

以下是一个在Windows平台上嵌入外部程序窗口至Qt应用程序中的基本示例:

首先,你需要启动一个外部程序并得到它的进程ID。然后,使用Windows API来查找窗口句柄并将其嵌入。

以下是示例代码:

#include <QProcess>
#include <QtWidgets>
#include <windows.h>

class MyWidget : public QWidget {
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // Start the external application
        QProcess *process = new QProcess(this);
        process->start("notepad.exe");

        // Wait for the process to start
        process->waitForStarted();

        // Now we need to find the window handle
        HWND hwnd = 0;
        while (!hwnd) {
            hwnd = FindWindowEx(0, hwnd, "Notepad", NULL);
            if (hwnd) {
                // Embed the window
                SetParent(hwnd, (HWND)this->winId());
                
                // Optionally resize the embedded window to fill the parent widget
                SetWindowPos(hwnd, 0, 0, 0, this->width(), this->height(), SWP_NOZORDER);
            }
            Sleep(10); // Prevents freezing
        }
    }

protected:
    // Overriding the resize event to resize the embedded window as well
    void resizeEvent(QResizeEvent *event) override {
        QWidget::resizeEvent(event);

        // Resize the embedded window to fill the parent widget
        HWND hwnd = FindWindowEx(0, 0, "Notepad", NULL);
        if (hwnd) {
            SetWindowPos(hwnd, 0, 0, 0, width(), height(), SWP_NOZORDER);
        }
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    MyWidget widget;
    widget.show();

    return app.exec();
}

在这个例子中,我们启动了记事本程序(notepad.exe),并且尝试获取它的窗口句柄。一旦我们得到窗口句柄,我们就调用SetParent来把记事本的窗口嵌入到我们的Qt窗口中,并且通过SetWindowPos函数调整窗口大小。

请注意,这个方法并不是通用的,因为它依赖于外部程序的窗口类名(本例中为"Notepad")和标题,这些信息可能会在不同版本的Windows或不同的应用程序中有所不同。此外,由于这种方法依赖于Windows API,它只适用于Windows平台。

此外,这种方法也可能会遇到安全限制,特别是在Windows Vista及以后的操作系统版本中,因为嵌入的窗口可能属于不同的进程。这可能需要调整UIPI(用户界面特权隔离)设置。

对于跨平台的Qt应用程序,可能需要寻找其他方法或考虑是否必须嵌入外部程序。

不知道你有没有发现有问题的地方

FindWindowEx & FindWindow

FindWindowFindWindowEx 是 Windows API 中的函数,用于在桌面上查找窗口,但它们在功能上有所不同。

FindWindow

FindWindow 函数用于查找顶级窗口,其类名和窗口名与指定的字符串匹配。这个函数不搜索子窗口。调用 FindWindow 时,你可以指定完整的窗口类名或窗口标题,或者两者都指定,也可以只指定其中之一(另一个参数设为 NULL)。

函数原型如下:

HWND FindWindow(
  LPCSTR lpClassName,  // 窗口类名
  LPCSTR lpWindowName  // 窗口标题
);

如果函数成功,返回值是目标窗口的句柄;如果未找到符合条件的窗口,返回 NULL

FindWindowEx

FindWindowEx 函数用于查找子窗口,它不仅可以查找顶级窗口,还可以查找子窗口,其功能比 FindWindow 更加灵活。使用 FindWindowEx 时,你可以指定父窗口句柄,从而在特定父窗口的子窗口中进行搜索。此外,你还可以通过传递前一个兄弟窗口的句柄来继续搜索。

函数原型如下:

HWND FindWindowEx(
  HWND    hWndParent,   // 父窗口句柄
  HWND    hWndChildAfter, // 子窗口句柄
  LPCSTR  lpszClass,    // 窗口类名
  LPCSTR  lpszWindow    // 窗口标题
);

hWndParent 参数是父窗口的句柄,hWndChildAfter 参数是子窗口的句柄,函数会从该子窗口后开始搜索。如果 hWndChildAfterNULL,搜索从 hWndParent 的第一个子窗口开始。如果 hWndParenthWndChildAfter 都是 NULL,那么 FindWindowEx 会搜索任何顶级窗口以及消息窗口。

FindWindowEx 的返回值和 FindWindow 一样,找到的窗口句柄或者 NULL

使用场景

  • 当你只需要查找顶级窗口时,使用 FindWindow 即可。
  • 当你需要在已知的父窗口中查找子窗口,或者需要在同一父窗口的子窗口间进行顺序查找时,使用 FindWindowEx

两者都是通过类名和窗口名进行匹配查找,但是 FindWindowEx 提供了更精细的控制,允许你对窗口的层级关系和搜索顺序进行精确的指定和遍历。
按照解释是没有任何问题,但是当我使用 FindWindowEx 找到的始终都是空,不得不转而使用 FindWindow

结束

到这里就基本走通了,如果你在开发中也遇到了找不到窗口的问题,而碰巧你也用了 FindWindowEx,那你不妨试一下 FindWindow

01-09 12:17