简单使用:
#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监听句柄变化的消息