CreateProcess和命令行参数

5

背景信息:Windows 7,Visual C++ 2010 Express

问题:CreateProcess() 返回“无效的命令行参数”

解释:我正在编写一段代码,使用 Windows API 的 CreateProcess 调用外部程序。到目前为止,我已经成功调用了一个外部程序:

if( !CreateProcess( "C:\\Temp\\convert.exe",
    t_str,        // Arguments
    ...
}
//where t_str is " C:\\img1.jpeg C:\\img1.pgm" (ImageMagick if you're wondering). 

即使我对数据进行了大量的处理,将所有内容推入Windows字符串和指针中,它仍然可以完美地工作。因此,我复制了所有用于CreateProcess()的混淆,以便调用另一个外部程序:

 if( !CreateProcess( "C:\\Temp\\sift.exe",
     t_str2,        // Arguments
     ...
 }
 //where t_str2 is ` < C:\\img1.pgm > C:\\img1.key`

基本上,两个调用非常相似,但是所有变量名都已更改(因为我正在串行运行这两个调用)。这就是问题所在:它不工作,而是打印出“无效的命令行参数:< C:\img1.pgm”。当然,这个命令在命令提示符中很好用,但在我的代码中却不好使。
我将t_str2换成了一些不那么复杂的东西(因为我知道sift.exe是如何工作的),结果还是一样。当我只运行sift而不是convert时也发生同样的事情。
问题是什么导致的?我该如何进一步排除这个问题?对于我使用的方法,有什么替代方案建议吗?任何帮助都将不胜感激。我可以提供更多代码,但它非常简单,不太可能出错。

一个想法:如果您使用参数 /c c:\temp\sift.exe < c:\img1.pmg > c:\img1.key 运行命令 cmd.exe,会怎样呢? - Aleks G
3个回答

9

您不能直接使用命令行重定向操作符与 CreateProcess()。 您必须生成 cmd.exe 的一个实例并将操作符传递给它,例如:

CreateProcess( "C:\\windows\\system32\\cmd.exe", t_str2, ...))

其中t_str2表示:"/C C:\\Temp\\sift.exe < C:\\img1.pgm > C:\\img1.key"。 实际的cmd.exe路径可以通过读取%COMSPEC%环境变量来确定。


4
在第二个例子中,您试图使用标准输入和输出重定向符号,这些符号是命令行应用程序的标记,但它们不是有效的程序参数。如果您想要使用重定向,您应该打开管道并手动从/向输入/输出文件读取和写入。在这里,您可以找到一个实现带有输入/输出重定向的进程创建的示例。

我并不是说CreateProcess是一个命令行处理器。我的意思是它不能将重定向作为进程的参数接受。如果想要使用重定向,就必须明确提供相应的代码。 - Eugene
我同意,我指出它为什么不能接受重定向作为参数...因为这是Windows命令行处理器的功能。 - user7116
啊,我明白了。这样更有意义。这是我第一次在Windows上认真学习编程。感谢您的见解。 - user850275

0

CreateProcess 有一些令人烦恼的问题,如果您没有参考官方文档,那么 Stack Exchange 上旧的答案可能会使这个过程变得有些麻烦。

  1. CreateProcess 的第一个参数在大多数情况下是可选的,这对于第一个参数来说真的很奇怪。如果您不想指定它,请使用 NULL,否则您应该阅读关于如何在不将其设置为 NULL 时使用参数 1 的非常具体的 文档
  2. 如果将参数 1 设置为 NULL,则应用程序需要成为参数 2 的第一部分。

再次感谢 Remy 清理了我最初回答中的奇怪行为。


这个代码示例只需要在 Windows 上使用基本的 VC++ 编译器和能够在桌面上创建和存储一个将被记事本打开的文件的能力。

如果不实际,可以自由使用 %temp% 或其他位置来放置测试文件。该应用程序将一直运行,直到您关闭 notepad.exe。这也处理获取和返回退出代码。如果您不希望它无限期地运行直到退出,则需要更新 WaitForSingleObject 行。

#include <Windows.h>

int main()
{
    STARTUPINFOA startup_info = { 0 };
    LPSTARTUPINFOA p_startup_info = &startup_info;
    PROCESS_INFORMATION proc_info = { 0 };
    LPPROCESS_INFORMATION p_proc_info = &proc_info;
    char command_line[] = 
      "C:\\Windows\\System32\\cmd.exe /C notepad.exe \"%USERPROFILE%\\Desktop\\test.txt\"";

    bool process_created = CreateProcess(
        NULL,
        command_line,
        NULL,
        NULL,
        FALSE,
        DETACHED_PROCESS,
        NULL,
        NULL,
        p_startup_info,
        p_proc_info
    );

    if (!process_created) { return -3; }

    DWORD process_exit;

    WaitForSingleObject(proc_info.hThread, INFINITE);

    GetExitCodeProcess(p_proc_info->hProcess, &process_exit);

    return (int)process_exit;
}

1
现在,如果您想要在CMD.exe中使用它,您需要在第一个参数中提供完整路径,并在第二个参数中包含来自第一个参数的应用程序。但是,使用第一个参数并不是必需的。您可以将第一个参数设置为NULL,然后CreateProcess()将从第二个参数解析要运行的EXE。这是更简单和更常见的情况,并且已经得到充分的记录。CreateProcess(NULL, "C:\\Windows\\System32\\cmd.exe <params>", ...); - Remy Lebeau
1
当编译为ANSI/MBCS时,您的(char*)类型转换是多余的,并且在编译为Unicode时会隐藏运行时错误。最好完全摆脱它。在后一种情况下,您无法将文字直接传递给参数(运行时崩溃),因此您应该首先将路径复制到本地的char[]/wchar_t[]数组中,然后再传递该数组。 - Remy Lebeau
哇!这是一个非常讨厌的小陷阱 :( 现在已经更新了!^_^ 非常感谢,@RemyLebeau! - kayleeFrye_onDeck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接