关于QProcess不能带空格的目录或文件

在Window系统下使用QProcess的start单独运行一个程序时。当程序路径存在空格会运行不成功的现象,最诡异的是,有时又可以运行。这到底是为什么呢?本文将以源码实现的角度来分析到底是为何?

1. 问题重现

  • 下列运行Test Demo.exe将会提示”系统找不到指定的文件。”
    1
    2
    QProcess process;
    process.start("C:/Users/Tmp/Test Demo.exe");

2. 解决方案

  • 解决调用程序不能带有空格的问题。

    2.1 使用使用空参数(arguments)的start接口

  • 接口:

    1
    2
    3
    void start(const QString &program, 
    const QStringList &arguments,
    QIODevice::OpenMode mode)
  • 示例:

    1
    2
    QProcess process;
    process.start("C:/Users/Tmp/Test Demo.exe", QStringList());

2.2 配合转义\字符的start接口

  • 接口:

    1
    void start(const QString &command, QIODevice::OpenMode mode)
  • 示例:

    1
    2
    QProcess process;
    process.start("\"C:/Users/Tmp/Test Demo.exe\"");

3. 为什么会这样?

  在问题重现错误例子中使用的start接口为:

1
void start(const QString &command, QIODevice::OpenMode mode)

  从接口(2.1与2.2)的相似度先提出疑问,为什么一个是program一个是command?
查看了QProcess分析得出program与command的区别是,前者不会对空格进行处理,而后者会把命令字符串以空格进行分割。
  假如command为C:/Users/Tmp/Test Demo.exe,实际上command会被分解为C:/Users/Tmp/TestDemo.exe。这样就会导致C:/Users/Tmp/Test文件找不到的现象。

4. 源码验证疑问

  • QProcess源码先来一波

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    static QStringList parseCombinedArgString(const QString &program)
    {
    QStringList args;
    QString tmp;
    int quoteCount = 0;
    bool inQuote = false;

    // handle quoting. tokens can be surrounded by double quotes
    // "hello world". three consecutive double quotes represent
    // the quote character itself.
    for (int i = 0; i < program.size(); ++i) {
    if (program.at(i) == QLatin1Char('"')) {
    ++quoteCount;
    if (quoteCount == 3) {
    // third consecutive quote
    quoteCount = 0;
    tmp += program.at(i);
    }
    continue;
    }
    if (quoteCount) {
    if (quoteCount == 1)
    inQuote = !inQuote;
    quoteCount = 0;
    }
    if (!inQuote && program.at(i).isSpace()) {
    if (!tmp.isEmpty()) {
    args += tmp;
    tmp.clear();
    }
    } else {
    tmp += program.at(i);
    }
    }
    if (!tmp.isEmpty())
    args += tmp;

    return args;
    }

    /*!
    \overload

    Starts the command \a command in a new process.
    The OpenMode is set to \a mode.

    \a command is a single string of text containing both the program name
    and its arguments. The arguments are separated by one or more spaces.
    For example:

    \snippet code/src_corelib_io_qprocess.cpp 5

    Arguments containing spaces must be quoted to be correctly supplied to
    the new process. For example:

    \snippet code/src_corelib_io_qprocess.cpp 6

    Literal quotes in the \a command string are represented by triple quotes.
    For example:

    \snippet code/src_corelib_io_qprocess.cpp 7

    After the \a command string has been split and unquoted, this function
    behaves like the overload which takes the arguments as a string list.

    You can disable this overload by defining \c
    QT_NO_PROCESS_COMBINED_ARGUMENT_START when you compile your applications.
    This can be useful if you want to ensure that you are not splitting arguments
    unintentionally, for example. In virtually all cases, using the other overload
    is the preferred method.

    On operating systems where the system API for passing command line
    arguments to a subprocess natively uses a single string (Windows), one can
    conceive command lines which cannot be passed via QProcess's portable
    list-based API. In these rare cases you need to use setProgram() and
    setNativeArguments() instead of this function.

    */
    #if !defined(QT_NO_PROCESS_COMBINED_ARGUMENT_START)
    void QProcess::start(const QString &command, OpenMode mode)
    {
    QStringList args = parseCombinedArgString(command);
    if (args.isEmpty()) {
    Q_D(QProcess);
    d->setErrorAndEmit(QProcess::FailedToStart, tr("No program defined"));
    return;
    }

    const QString prog = args.takeFirst();

    start(prog, args, mode);
    }
    #endif
  • 从上面源码可以看到start(const QString &command, OpenMode mode)接口内部使用到了parseCombinedArgString接口,而这一接口作用是检查字符串以可空格分解参数。

  • 不想命令行被以空格为分解,则不要使用该接口。

5. 怎么避免混用这两个相似的start接口?

  • 在项目(.pro)文件添加以屏蔽start(const QString &command, OpenMode mode)接口的使用。
  • 使用setProgram()setNativeArguments()也能设置命令与参数。
    1
    DEFINES += QT_NO_PROCESS_COMBINED_ARGUMENT_START

6. 关于更新

  • 文章首发于微信公众号你才小学生
  • 后续更新于Qtbig哥(qtbig.com)