CreateProcess无法传递命令行参数

34

你好,我有以下代码,但它并没有按照预期工作,我无法找出问题在哪里。

基本上,我正在执行一个过程(.NET过程)并传递命令行参数,CreateProcess()成功执行了它,但CreateProcess()未传递命令行参数。

我在这里做错了什么?

int main(int argc, char* argv[])
{
    PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter

    STARTUPINFO StartupInfo; //This is an [in] parameter

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field

    LPTSTR cmdArgs = "name@example.com";

    if(CreateProcess("D:\\email\\smtp.exe", cmdArgs, 
        NULL,NULL,FALSE,0,NULL,
        NULL,&StartupInfo,&ProcessInfo))
    { 
        WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);

        printf("Yohoo!");
    }  
    else
    {
        printf("The process could not be started...");
    }

    return 0;
}

编辑: 嘿,还有一件事,如果我像这样传递我的cmdArgs

// a space as the first character
LPTSTR cmdArgs = " name@example.com";

然后我遇到了错误,然后CreateProcess返回TRUE但我的目标进程没有被执行。

Object reference not set to an instance of an object

如何检测参数是否未传递? - sharptooth
我检查执行过程的输出,如果没有传递任何参数,目标进程会打印一个错误并终止。 - akif
我想可能还有其他问题。您能否在程序开始时插入一个延迟,并在启动后附加调试器? - sharptooth
在程序的开头(static void Main()函数)插入类似System.Threading.Thread.Sleep(20000);的语句,并在该语句后面的行上设置断点。然后编译C#程序并让主程序启动C#程序。当C#程序启动时,它将在该语句上暂停20秒钟 - 这应该足够你在VisualStudio中执行Tools->Debug Processes并附加到C#程序。当20秒钟过去时,C#程序将在调试器中停止,然后您可以逐步调试它。 - sharptooth
这篇来自微软的解释可能会有所帮助,对我很有用,当时也处于同样的困惑中。http://support.microsoft.com/kb/175986 - Andrei-Niculae Petre
8个回答

29

在参数中应该同时指定模块名称:LPTSTR cmdArgs = "App name@example.com"; 它应该是整个命令行(包括argv[0])。


我收到了错误信息对象引用未设置为对象的实例 - akif
1
这很有帮助。如果我没有遇到这个答案,我永远不会知道。 - david.healed
3
我必须说MSDN在这方面并不太清晰:“因为argv[0]是模块名称,所以C程序员通常会将模块名称重复作为命令行的第一个标记。”和“如果lpApplicationName为NULL,则命令行的第一个以空格分隔的标记指定了模块名称。”但后者似乎不适用,因为已经提供了lpApplicationName - Deanna
6
很多程序都会从argv [1]开始评估参数,因为argv [0]通常包含模块名称。如果命令行中没有模块名称,它会缺失。最终你会丢失第一个参数,这是一个大问题,我花了很多时间才找出来。 - Alexander Drichel

25
如果CreateProcess()的第一个参数非空,它将使用该参数来定位要启动的映像。
如果为NULL,则会解析第二个参数以尝试从第一个标记中获取要启动的可执行文件。
在任一情况下,C运行时将使用第二个参数填充argv数组。因此,该参数的第一个标记出现在argv [0]中。
您可能需要类似以下的内容(我已将smtp.exe程序更改为echoargs.exe - 这是我有助于确定这种问题的简单实用工具):
int main(int argc, char* argv[])
{
    PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter

    STARTUPINFO StartupInfo; //This is an [in] parameter
    char cmdArgs[] = "echoargs.exe name@example.com";

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field


    if(CreateProcess("C:\\util\\echoargs.exe", cmdArgs, 
        NULL,NULL,FALSE,0,NULL,
        NULL,&StartupInfo,&ProcessInfo))
    { 
        WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);

        printf("Yohoo!");
    }  
    else
    {
        printf("The process could not be started...");
    }

    return 0;
}

下面是我从该程序中得到的输出:

echoargs.exe name@example.com
[0]: echoargs.exe
[1]: name@example.com

Yohoo!

1
他是正确的。我甚至在Win32 CreateProcess()中使用了这个解决方案,它按预期工作。谢谢你啊。 - gramcha
第二个参数应该是指向可修改内存的指针。 - David Heffernan

7

看起来您没有正确使用CreateProcess函数,请参考http://msdn.microsoft.com/en-us/library/ms682425%28VS.85%29.aspx了解更多详情。

  • 要执行的命令行。该字符串的最大长度为32,768个字符,包括Unicode终止空字符。如果lpApplicationName为NULL,则lpCommandLine的模块名部分限制为MAX_PATH个字符。

  • lpCommandLine参数可以为NULL。在这种情况下,函数将使用lpApplicationName指向的字符串作为命令行。

  • 如果lpApplicationName和lpCommandLine都不为NULL,则lpApplicationName指向的以null结尾的字符串指定要执行的模块,而lpCommandLine指向的以null结尾的字符串指定命令行。新进程可以使用GetCommandLine来检索整个命令行。使用C语言编写的控制台进程可以使用argc和argv参数解析命令行。因为argv [0]是模块名,所以C程序员通常会在命令行中重复模块名作为第一个标记。

因此,在您的情况下,您需要将此作为命令参数,并且应该传递NULL作为第一个参数,以获得所需的行为。

// NOTE THE Null-Terminated string too!
LPTSTR cmdArgs = "D:\\email\\smtp.exe name@example.com\0";

5
代码片段末尾定义了一个带有两个空终止符的字符串 - 没有必要显式地添加第二个。 - Daniel Earwicker
我收到了错误信息对象引用未设置为对象的实例 - akif
1
@Earwicker,我已经很久没有写C++了,但结果还是一样的... - Ray Hayes

6
以下是Zeus IDE用于运行外部进程的代码的简化版本:
bool createProcess(const char *pszTitle, const char *pszCommand)
{
  STARTUPINFO StartInfo;

  memset(&StartInfo, 0, sizeof(StartInfo));

  StartInfo.cb      = sizeof(StartInfo);
  StartInfo.lpTitle = (pszTitle) ? (char *)pszTitle : (char *)pszCommand;

  StartInfo.wShowWindow = SW_NORMAL;
  StartInfo.dwFlags    |= STARTF_USESHOWWINDOW;

  if (CreateProcess(0, (char *)pszCommand, 
                    0, 0, TRUE,
                    CREATE_NEW_PROCESS_GROUP, 0, 0, 
                    &StartInfo, &ProcessInfo))
  {
    lErrorCode = 0;
  }
  else
  {
    lErrorCode = GetLastError();
  }

  return (lErrorCode == 0);
}

pszCommand 是完整的可执行文件路径、文件名和参数,例如:

pszCommand = "D:\\email\\smtp.exe name@example.com";

据我所知,这两者之间唯一的真正区别在于,在Zeus示例中,dwCreationFlags参数设置为CREATE_NEW_PROCESS_GROUP值。

1
const char * 强制转换为 char * 是危险的,并会引发未定义行为。 - phuclv
我倾向于认为在这种情况下可以使用。理由是:CreateProcess 的第二个参数是 LPTSTR(而不是 LPCTSTR),这可能是因为 main(int argc, char *argv[])。该 API 不会复制参数。总之,只要被调用的进程不违反其 argv[](char*) 就应该没问题。 - Andreas Spindler
@jussij,pszTitle由什么组成?它存储了哪些数据? - Elvin

4

您可以在cmdArgs字符串的第一个字符前添加一个空格:

LPTSTR cmdArgs = " name@example.com";

显然,Windows将第二个参数字符串附加到由第一个参数表示的应用程序名称中,并将结果作为命令行参数传递给可执行文件。因此,添加空格将正确地分隔参数。


3

试试这个:

LPTSTR cmdArgs = "name@example.com";
CString szcmdline("D:\\email\\smtp.exe");
szcmdline += _T(" ") + cmdArgs ;

//Leave first param empty and pass path + argms in 
    if(CreateProcess(NULL, szcmdline, second

那段代码出错了,你把值放到了szcmdline_in里面,但是却在使用szcmdline。为什么要在这个例子中引入MFC字符串呢? - Ray Hayes
没有特别的原因,这只是象征性的。我会将“szcmdline_in”更改为“szcmdline”。谢谢。 - aJ.

1
该函数的Unicode版本CreateProcessW可以修改此字符串的内容。因此,此参数不能是指向只读内存(例如const变量或文字字符串)的指针。如果此参数是常量字符串,则该函数可能会导致访问冲突。
因此,您可以尝试使用LPTSTR cmdArgs = _tcsdup("name@example.com")。
另一个问题是:目标进程如何读取参数?使用argv[0]作为应用程序名称?那么您也应将应用程序名称附加为第一个参数。

1
或者将内存管理责任退回到应该在的地方,编译器:TCHAR [] cmdArgs = _T("name@example.com"); - MSalters
这里有一种虚假的安全感。问问自己为什么要围绕API弯曲,而微软30年来都没有修复它。正如我之前提到的,很可能Windows需要一个char*(或LPTSTR),因为有int main(int argc, char* argv[])。想想看。如果被调用者写入argv[],Win32 API就没有真正的解决方案。实际上,通过声明LPTSTR,留下了这个选项。 - Andreas Spindler
新进程在不同的地址空间中运行;它对 argv 所做的更改不会以任何方式影响 CreateProcess() 的调用者。 - user2894959

0

你没有为字符串分配内存。

应该改为:

LPTSTR cmdArgs = "name@example.com";

尝试:

TCHAR cmdArgs[] = "name@example.com";

编辑:

然后调用:
 CreateProcess("D:\\email\\smtp.exe", &cmdArgs[0], ...

这将在堆栈上创建一个本地数组,然后传递该数组的指针。

错误 C2440:'initializing':无法将类型为 'char (*)[20]' 的参数转换为 'char *' 类型 - akif
实际上只需要 cmdArgs 就可以了;&cmdArgs[0] 只是说同样的事情的一种更复杂的方式。 - Harry Johnston
清除你的概念,常量字符串字面值不需要像数组char str[]一样进行内存分配,你可以使用指针,例如char *str = "Arguements",这是更好的解决方案。 - Haseeb Mir

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