QProcess 类用于启动外部程序并与它们进行通信。
官方调用例子:
QObject *parent;
...
QString program = "./path/to/Qt/examples/widgets/analogclock";
QStringList arguments;
arguments << "-style" << "fusion";
QProcess *myProcess = new QProcess(parent);
myProcess->start(program, arguments);
可以看到最终是调用start()函数执行外部程序。
下面分析下start()函数的调用。
void start(const QString &program, const QStringList &arguments, OpenMode mode = ReadWrite);
可见最后一个参数是默认参数。
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);
}
void QProcess::start(OpenMode mode)
{
Q_D(QProcess);
if (d->processState != NotRunning) {
qWarning("QProcess::start: Process is already running");
return;
}
if (d->program.isEmpty()) {
d->setErrorAndEmit(QProcess::FailedToStart, tr("No program defined"));
return;
}
d->start(mode);
}
void QProcessPrivate::start(QIODevice::OpenMode mode)
{
Q_Q(QProcess);
#if defined QPROCESS_DEBUG
qDebug() << "QProcess::start(" << program << ',' << arguments << ',' << mode << ')';
#endif
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();
}
调用startProcess()函数时会根据操作系统的不同有不同的实现。
我们看下在linux系统下的实现。
void QProcessPrivate::startProcess()
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::startProcess()");
#endif
// Initialize pipes
if (!openChannel(stdinChannel) ||
!openChannel(stdoutChannel) ||
!openChannel(stderrChannel) ||
qt_create_pipe(childStartedPipe) != 0) {
setErrorAndEmit(QProcess::FailedToStart, qt_error_string(errno));
cleanup();
return;
}
if (threadData->hasEventDispatcher()) {
startupSocketNotifier = new QSocketNotifier(childStartedPipe[0],
QSocketNotifier::Read, q);
QObject::connect(startupSocketNotifier, SIGNAL(activated(int)),
q, SLOT(_q_startupNotification()));
}
// Start the process (platform dependent)
q->setProcessState(QProcess::Starting);
// Create argument list with right number of elements, and set the final
// one to 0.
char **argv = new char *[arguments.count() + 2];
argv[arguments.count() + 1] = 0;
// Encode the program name.
QByteArray encodedProgramName = QFile::encodeName(program);
#ifdef Q_OS_MAC
// allow invoking of .app bundles on the Mac.
QFileInfo fileInfo(program);
if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) {
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0,
QCFString(fileInfo.absoluteFilePath()),
kCFURLPOSIXPathStyle, true);
{
// CFBundle is not reentrant, since CFBundleCreate might return a reference
// to a cached bundle object. Protect the bundle calls with a mutex lock.
static QBasicMutex cfbundleMutex;
QMutexLocker lock(&cfbundleMutex);
QCFType<CFBundleRef> bundle = CFBundleCreate(0, url);
// 'executableURL' can be either relative or absolute ...
QCFType<CFURLRef> executableURL = CFBundleCopyExecutableURL(bundle);
// not to depend on caching - make sure it's always absolute.
url = CFURLCopyAbsoluteURL(executableURL);
}
if (url) {
const QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
encodedProgramName += (QDir::separator() + QDir(program).relativeFilePath(QString::fromCFString(str))).toUtf8();
}
}
#endif
// Add the program name to the argument list.
argv[0] = nullptr;
if (!program.contains(QLatin1Char('/'))) {
const QString &exeFilePath = QStandardPaths::findExecutable(program);
if (!exeFilePath.isEmpty()) {
const QByteArray &tmp = QFile::encodeName(exeFilePath);
argv[0] = ::strdup(tmp.constData());
}
}
if (!argv[0])
argv[0] = ::strdup(encodedProgramName.constData());
// Add every argument to the list
for (int i = 0; i < arguments.count(); ++i)
argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData());
// Duplicate the environment.
int envc = 0;
char **envp = 0;
if (environment.d.constData()) {
envp = _q_dupEnvironment(environment.d.constData()->vars, &envc);
}
// Encode the working directory if it's non-empty, otherwise just pass 0.
const char *workingDirPtr = 0;
QByteArray encodedWorkingDirectory;
if (!workingDirectory.isEmpty()) {
encodedWorkingDirectory = QFile::encodeName(workingDirectory);
workingDirPtr = encodedWorkingDirectory.constData();
}
// Start the process manager, and fork off the child process.
pid_t childPid;
forkfd = ::forkfd(FFD_CLOEXEC, &childPid);
int lastForkErrno = errno;
if (forkfd != FFD_CHILD_PROCESS) {
// Parent process.
// Clean up duplicated memory.
for (int i = 0; i <= arguments.count(); ++i)
free(argv[i]);
for (int i = 0; i < envc; ++i)
free(envp[i]);
delete [] argv;
delete [] envp;
}
// On QNX, if spawnChild failed, childPid will be -1 but forkfd is still 0.
// This is intentional because we only want to handle failure to fork()
// here, which is a rare occurrence. Handling of the failure to start is
// done elsewhere.
if (forkfd == -1) {
// Cleanup, report error and return
#if defined (QPROCESS_DEBUG)
qDebug("fork failed: %ls", qUtf16Printable(qt_error_string(lastForkErrno)));
#endif
q->setProcessState(QProcess::NotRunning);
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno)));
cleanup();
return;
}
// Start the child.
if (forkfd == FFD_CHILD_PROCESS) {
execChild(workingDirPtr, argv, envp);
::_exit(-1);
}
pid = Q_PID(childPid);
// parent
// close the ends we don't use and make all pipes non-blocking
qt_safe_close(childStartedPipe[1]);
childStartedPipe[1] = -1;
if (stdinChannel.pipe[0] != -1) {
qt_safe_close(stdinChannel.pipe[0]);
stdinChannel.pipe[0] = -1;
}
if (stdinChannel.pipe[1] != -1)
::fcntl(stdinChannel.pipe[1], F_SETFL, ::fcntl(stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK);
if (stdoutChannel.pipe[1] != -1) {
qt_safe_close(stdoutChannel.pipe[1]);
stdoutChannel.pipe[1] = -1;
}
if (stdoutChannel.pipe[0] != -1)
::fcntl(stdoutChannel.pipe[0], F_SETFL, ::fcntl(stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK);
if (stderrChannel.pipe[1] != -1) {
qt_safe_close(stderrChannel.pipe[1]);
stderrChannel.pipe[1] = -1;
}
if (stderrChannel.pipe[0] != -1)
::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK);
if (threadData->eventDispatcher.loadAcquire()) {
deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q);
QObject::connect(deathNotifier, SIGNAL(activated(int)),
q, SLOT(_q_processDied()));
}
}
先把程序名也加到参数列表中,然后Fork出一个子进程,调用子进程execChild(workingDirPtr, argv, envp);