QProcess

简单使用:

#pragma once

#include <QtWidgets/QWidget>
#include "ui_widget_server.h"
#include <QProcess>
class widget_server : public QWidget
{
    Q_OBJECT

public:
    widget_server(QWidget *parent = Q_NULLPTR);
    void onButtonClicked();

public slots:
    void finished(int exitCode, QProcess::ExitStatus exitStatus);
    void stateChanged(QProcess::ProcessState newState);
    void started();
private:
    Ui::widget_serverClass ui;
    QProcess *process = nullptr;
};
#include "widget_server.h"
#include <QPushbutton>
#include <QProcess>
#include <QDebug>
widget_server::widget_server(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    connect(ui.pushButton, &QPushButton::clicked, this, &widget_server::onButtonClicked);
}

void widget_server::onButtonClicked() {
  if (process == nullptr) {
    process = new QProcess(this);
 
    connect(process, SIGNAL(started()), this,SLOT(started()));

    connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),this, SLOT(finished(int, QProcess::ExitStatus)));

    connect(process, SIGNAL(stateChanged(QProcess::ProcessState)), this,SLOT(stateChanged(QProcess::ProcessState)));
  }

  QStringList list;
  //list << "hello_1" << "world_2" << "ok_3";

  QString program = "C:\\Windows\\notepad.exe";

  process->start(program, list);

}

void widget_server::finished(int exitCode, QProcess::ExitStatus exitStatus)
{
  qDebug() << "finished";

  qDebug() << exitCode;// 被调用程序的main返回的int
  qDebug() << exitStatus;// QProcess::ExitStatus(NormalExit)
  qDebug() << "finished-output-readAll:";
  qDebug() << QString::fromLocal8Bit(process->readAll());
  qDebug() << "finished-output-readAllStandardOutput:";
  qDebug() << QString::fromLocal8Bit(process->readAllStandardOutput());

}

void widget_server::started() {
  qDebug() << "started";
}
void widget_server::stateChanged(QProcess::ProcessState state) {
  qDebug() << "stateChanged";
  qDebug() << state;// 被调用程序的main返回的int
}

当notepad.exe退出时,会收到finished信号。那么QProcess是如何运行的呢?

void QProcess::start(const QString &program, const QStringList &arguments, OpenMode mode)
  • 在新进程中启动给定的program,传递命令行 arguments中的参数。QProcess 对象将立即进入开始状态。
  • 如果进程启动成功,QProcess 会发出started();否则,errorOccurred() 将被发出。
  • 进程是异步启动的,这意味着started()和 errorOccurred() 信号可能会延迟。调用 waitForStarted() 使确保进程已启动(或启动失败)和那些信号已经发出。
  •  如果 QProcess 对象已经在运行一个进程,则可能会出现警告
  •  在控制台打印,现有进程将继续运行不受影响。
void QProcess::start(const QString &program, const QStringList &arguments, OpenMode mode)
{
    Q_D(QProcess);
    if (d->processState != NotRunning) {
        qWarning("QProcess::start: Process is already running");
        return;
    }
    if (program.isEmpty()) {
        d->setErrorAndEmit(QProcess::FailedToStart, tr("No program defined"));
        return;
    }

    d->program = program;//可执行文件路径
    d->arguments = arguments;//参数

    d->start(mode);
}

d即QProcessPrivate,d->start 到了:

void QProcessPrivate::start(QIODevice::OpenMode mode)
{
    Q_Q(QProcess);
#if defined QPROCESS_DEBUG
    qDebug() << "QProcess::start(" << program << ',' << arguments << ',' << mode << ')';
#endif

    //获取创建进程stdin stdout stderr 
    if (stdinChannel.type != QProcessPrivate::Channel::Normal)
        mode &= ~QIODevice::WriteOnly;     // not open for writing
    if (stdoutChannel.type != QProcessPrivate::Channel::Normal &&
        (stderrChannel.type != QProcessPrivate::Channel::Normal ||
         processChannelMode == QProcess::MergedChannels))
        mode &= ~QIODevice::ReadOnly;      // not open for reading
    if (mode == 0)
        mode = QIODevice::Unbuffered;
    if ((mode & QIODevice::ReadOnly) == 0) {
        if (stdoutChannel.type == QProcessPrivate::Channel::Normal)
            q->setStandardOutputFile(q->nullDevice());
        if (stderrChannel.type == QProcessPrivate::Channel::Normal
            && processChannelMode != QProcess::MergedChannels)
            q->setStandardErrorFile(q->nullDevice());
    }

    q->QIODevice::open(mode);

    if (q->isReadable() && processChannelMode != QProcess::MergedChannels)
        setReadChannelCount(2);

    stdinChannel.closed = false;
    stdoutChannel.closed = false;
    stderrChannel.closed = false;

    exitCode = 0;
    exitStatus = QProcess::NormalExit;
    processError = QProcess::UnknownError;
    errorString.clear();
    startProcess();//启动进程
}

void QProcessPrivate::startProcess()

void QProcessPrivate::startProcess()
{
    Q_Q(QProcess);

    bool success = false;

    if (pid) {
        CloseHandle(pid->hThread);
        CloseHandle(pid->hProcess);
        delete pid;
        pid = 0;
    }
    pid = new PROCESS_INFORMATION;
    memset(pid, 0, sizeof(PROCESS_INFORMATION));

    q->setProcessState(QProcess::Starting);

    if (!openChannel(stdinChannel) ||
        !openChannel(stdoutChannel) ||
        !openChannel(stderrChannel))
        return;

    QString args = qt_create_commandline(program, arguments);
    QByteArray envlist;
    if (environment.d.constData())
        envlist = qt_create_environment(environment.d.constData()->vars);
    if (!nativeArguments.isEmpty()) {
        if (!args.isEmpty())
             args += QLatin1Char(' ');
        args += nativeArguments;
    }

#if defined QPROCESS_DEBUG
    qDebug("Creating process");
    qDebug("   program : [%s]", program.toLatin1().constData());
    qDebug("   args : %s", args.toLatin1().constData());
    qDebug("   pass environment : %s", environment.isEmpty() ? "no" : "yes");
#endif

    // We cannot unconditionally set the CREATE_NO_WINDOW flag, because this
    // will render the stdout/stderr handles connected to a console useless
    // (this typically affects ForwardedChannels mode).
    // However, we also do not want console tools launched from a GUI app to
    // create new console windows (behavior consistent with UNIX).
    DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
    dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
    STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
                                 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
                                 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
                                 0, 0, 0,
                                 STARTF_USESTDHANDLES,
                                 0, 0, 0,
                                 stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1]
    };

    const QString nativeWorkingDirectory = QDir::toNativeSeparators(workingDirectory);
    QProcess::CreateProcessArguments cpargs = {
        0, (wchar_t*)args.utf16(),
        0, 0, TRUE, dwCreationFlags,
        environment.isEmpty() ? 0 : envlist.data(),
        nativeWorkingDirectory.isEmpty() ? Q_NULLPTR : (wchar_t*)nativeWorkingDirectory.utf16(),
        &startupInfo, pid
    };
    if (modifyCreateProcessArgs)
        modifyCreateProcessArgs(&cpargs);
    //创建进程
    success = CreateProcess(cpargs.applicationName, cpargs.arguments, cpargs.processAttributes,
                            cpargs.threadAttributes, cpargs.inheritHandles, cpargs.flags,
                            cpargs.environment, cpargs.currentDirectory, cpargs.startupInfo,
                            cpargs.processInformation);

    QString errorString;
    if (!success) {
        // Capture the error string before we do CloseHandle below
        errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
    }

    if (stdinChannel.pipe[0] != INVALID_Q_PIPE) {
        CloseHandle(stdinChannel.pipe[0]);
        stdinChannel.pipe[0] = INVALID_Q_PIPE;
    }
    if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) {
        CloseHandle(stdoutChannel.pipe[1]);
        stdoutChannel.pipe[1] = INVALID_Q_PIPE;
    }
    if (stderrChannel.pipe[1] != INVALID_Q_PIPE) {
        CloseHandle(stderrChannel.pipe[1]);
        stderrChannel.pipe[1] = INVALID_Q_PIPE;
    }

    if (!success) {
        cleanup();
        setErrorAndEmit(QProcess::FailedToStart, errorString);
        q->setProcessState(QProcess::NotRunning);
        return;
    }

    q->setProcessState(QProcess::Running);
    // User can call kill()/terminate() from the stateChanged() slot
    // so check before proceeding
    if (!pid)
        return;

    //消息通知
    if (threadData->hasEventDispatcher()) {
        processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
        QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied()));
        processFinishedNotifier->setEnabled(true);
    }

    _q_startupNotification();
}

其中:CreateProcess https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa 调用windowapi创建进程;

qt中的消息循环是线程相关的https://zhuanlan.zhihu.com/p/113695485,简单的说

当我们创建一个QObject时,它会与创建自己所在的线程绑定。它参与的消息循环,其实是它所在线程的消息循环,如上图所示。假如某个线程没有默认的QThread::exec(),那么该线程上的QObject则无法接收到事件。另外,如果两个不同线程的QObject需要相互通信,那么只能通过QueuedConnection的方式,异步通知对方线程,在下一轮消息循环处理QObject的消息。

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher)
        return false;
    if (flags & DeferredDeletion)
        QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    return d->threadData->eventDispatcher->processEvents(flags);
}

原来QEventLoop作为一个QObject,它也有threadData。同一个线程threadData只创建一次,所以它们取出来的eventDispatcher也都是相同的。这意味着所有的相同线程的QObject,共享一份threadData,也就是同一份eventDispatcher, postEventList等。这也就说明了,我们上图是如何实现的

QEventDispatcherWin32是跟着线程走的,所以没有必要每个QEventLoop都存一个。事实上,它存放在一个叫做QThreadData的结构中

    if (threadData->hasEventDispatcher()) {
        processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
        QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied()));
        processFinishedNotifier->setEnabled(true);
    }

重点是:        processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);它创建了一个QWinEventNotifier,并且注册到eventDispatcher,也就是QEventDispatcherWin32

QWinEventNotifier::QWinEventNotifier(HANDLE hEvent, QObject *parent)
 : QObject(*new QWinEventNotifierPrivate(hEvent, false), parent)
{
    Q_D(QWinEventNotifier);
    QAbstractEventDispatcher *eventDispatcher = d->threadData->eventDispatcher.load();
    if (Q_UNLIKELY(!eventDispatcher)) {
        qWarning("QWinEventNotifier: Can only be used with threads started with QThread");
        return;
    }
    eventDispatcher->registerEventNotifier(this);
    d->enabled = true;
}

接下来到了:  eventDispatcher->registerEventNotifier(this);

bool QEventDispatcherWin32::registerEventNotifier(QWinEventNotifier *notifier)
{
    if (!notifier) {
        qWarning("QWinEventNotifier: Internal error");
        return false;
    } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
        qWarning("QWinEventNotifier: event notifiers cannot be enabled from another thread");
        return false;
    }

    Q_D(QEventDispatcherWin32);

    if (d->winEventNotifierList.contains(notifier))
        return true;

    if (d->winEventNotifierList.count() >= MAXIMUM_WAIT_OBJECTS - 2) {
        qWarning("QWinEventNotifier: Cannot have more than %d enabled at one time", MAXIMUM_WAIT_OBJECTS - 2);
        return false;
    }
    d->winEventNotifierList.append(notifier);//加入到列表中QList
    return true;
}

消息循环:

 进入到

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherWin32);

    if (!d->internalHwnd) {
        createInternalHwnd();
        wakeUp(); // trigger a call to sendPostedEvents()
    }

......
    do {
......
                haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
......

                if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }


......
            DWORD nCount = d->winEventNotifierList.count();//事件通知者列表
            Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
            for (int i=0; i<(int)nCount; i++)
                pHandles[i] = d->winEventNotifierList.at(i)->handle();

            emit aboutToBlock();
            waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);//监听handle是否发生变化
            emit awake();
            if (waitRet - WAIT_OBJECT_0 < nCount) {
                d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));//激活对应的事件通知者
                retVal = true;
            }
        }
    } while (canWait);

......

    return retVal;
}

当监听到QProcess创建的进程句柄不存在时,

activateEventNotifier被调用:发送了一个QEvent::WinEventAct,sendEvent直接发送到到对方

void QEventDispatcherWin32Private::activateEventNotifier(QWinEventNotifier * wen)
{
    QEvent event(QEvent::WinEventAct);
    QCoreApplication::sendEvent(wen, &event);
}

中间经过:

 最终进入,之前创建的QWinEventNotifier::event中

bool QWinEventNotifier::event(QEvent * e)
{
    Q_D(QWinEventNotifier);
    if (e->type() == QEvent::ThreadChange) {
        if (d->enabled) {
            QMetaObject::invokeMethod(this, "setEnabled", Qt::QueuedConnection,
                                      Q_ARG(bool, true));
            setEnabled(false);
        }
    }
    QObject::event(e);                        // will activate filters
    if (e->type() == QEvent::WinEventAct) {
        emit activated(d->handleToEvent, QPrivateSignal());
        return true;
    }
    return false;
}

当收到 QWinEventNotifier收到QEvent::WinEventAct,则发送 emit activated(d->handleToEvent, QPrivateSignal());记得在QProess 中start中,连接了一个信号

    if (threadData->hasEventDispatcher()) {
        processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
        QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied()));
        processFinishedNotifier->setEnabled(true);
    }

其中,q即QProcess,这段代码在void QProcessPrivate::startProcess()中;

因此最终槽bool QProcessPrivate::_q_processDied()被调用;

bool QProcessPrivate::_q_processDied()
{
    Q_Q(QProcess);
#if defined QPROCESS_DEBUG
    qDebug("QProcessPrivate::_q_processDied()");
#endif
#ifdef Q_OS_UNIX
    if (!waitForDeadChild())
        return false;
#endif
#ifdef Q_OS_WIN
    if (processFinishedNotifier)
        processFinishedNotifier->setEnabled(false);
    drainOutputPipes();
#endif

    // the process may have died before it got a chance to report that it was
    // either running or stopped, so we will call _q_startupNotification() and
    // give it a chance to emit started() or errorOccurred(FailedToStart).
    if (processState == QProcess::Starting) {
        if (!_q_startupNotification())
            return true;
    }

    if (dying) {
        // at this point we know the process is dead. prevent
        // reentering this slot recursively by calling waitForFinished()
        // or opening a dialog inside slots connected to the readyRead
        // signals emitted below.
        return true;
    }
    dying = true;

    // in case there is data in the pipe line and this slot by chance
    // got called before the read notifications, call these two slots
    // so the data is made available before the process dies.
    _q_canReadStandardOutput();
    _q_canReadStandardError();

    findExitCode();

    if (crashed) {
        exitStatus = QProcess::CrashExit;
        setErrorAndEmit(QProcess::Crashed);
    }

    bool wasRunning = (processState == QProcess::Running);

    cleanup();

    if (wasRunning) {
        // we received EOF now:
        emit q->readChannelFinished();
        // in the future:
        //emit q->standardOutputClosed();
        //emit q->standardErrorClosed();

        emit q->finished(exitCode);//
        emit q->finished(exitCode, exitStatus);
    }
#if defined QPROCESS_DEBUG
    qDebug("QProcessPrivate::_q_processDied() process is dead");
#endif
    return true;
}

这段代码会调用:

void QProcessPrivate::findExitCode()
{
    DWORD theExitCode;
    Q_ASSERT(pid);
    if (GetExitCodeProcess(pid->hProcess, &theExitCode)) {
        exitCode = theExitCode;
        crashed = (exitCode == 0xf291   // our magic number, see killProcess
                   || (theExitCode >= 0x80000000 && theExitCode < 0xD0000000));
    

获取创建进程 的退出状态,然后根据退出状态的不同发送各种信号;

监听QPricess::finished信号依赖于qt的消息循环,如果没有启动qt的消息循环,则无法进行ProcessEvent监听句柄变化的消息

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值