2

考虑以下命令行参数

"alfa" "beta" "4"

当我为我正在工作的项目指定 Run>Parameters... 时,应用程序在 Process Explorer 上显示为命令行:

"c:\myapp\myapp.exe" "alfa" "beta" "4"

ParamCount 显示 4 个参数。但是,当我从启动器应用程序(进行访问控制)启动相同的可执行文件时,Process Explorer 显示:

"alfa" "beta" "4"

ParamCount 显示 3 个参数。命令行是从启动器应用程序中提取的。理论上它会起作用,因为从启动器启动时,应用程序可以完美地工作。从 IDE 启动时,它会尝试在"4"上面执行 StrToInt,但只检索"beta"参数。

启动器应用程序的示例代码:

var
  StartupInfo: TSTARTUPINFO;
  ProcessInfo: PROCESS_INFORMATION;
  CurrentDirPath: String;
begin
  Result := 0;
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := SizeOf(StartupInfo);
  DirCorrente := ExtractFilePath(sExe);

  if CreateProcess(PChar(sExe), PChar(sParam), nil, nil, true,
    NORMAL_PRIORITY_CLASS, nil, PChar(CurrentDirPath),
    StartupInfo, ProcessInfo) then

sParam 的内容是上面的命令行参数,sExe 是可执行路径。为什么会发生这种情况?

注意:我已经设计了如何将命令行参数解释更改为对于这种极端情况是稳健的——这里的重点是为什么会发生这种情况。

4

2 回答 2

5

您的启动程序未CreateProcess正确调用。考虑以下文档摘录(已添加重点):

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

忽略关于“C程序员”的部分;它适用于为 Windows 编写程序的每个人,无论使用哪种语言。

您的启动器为 lpApplicationName 和 lpCommandLine 参数提供了值,但它没有遵循将程序文件名重复为命令行中的第一个参数的约定。DelphiParamStrParamCount函数知道遵循约定,因此它们跳过命令行上的第一个标记。如果调用者没有遵循约定,那么接收者最终会认为预期的第二个参数实际上是第一个,第三个实际上是第二个,依此类推。

于 2015-08-26T21:13:37.627 回答
3

第二个参数按原样作为命令行传递给启动的进程。大多数 RTL(包括 Delphi)期望命令行中的第一个分隔值是 EXE 路径。这在CreateProcess()文档中有所说明:

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

当用户启动可执行文件时,操作系统会自动处理它,但应用程序必须在通过代码启动进程时手动管理它。

启动器不包括 EXE 路径作为它传递到的命令行的第一个分隔值CreateProcess()。它需要这样做:

var
  StartupInfo: TSTARTUPINFO;
  ProcessInfo: PROCESS_INFORMATION;
  ...
  CmdLine: String;
begin
  Result := 0;
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := SizeOf(StartupInfo);
  ...
  CmdLine := TrimRight(AnsiQuotedStr(sExe, '"') + ' ' + sParam);
  ...    
  if CreateProcess(PChar(sExe), PChar(CmdLine), ...) then

CreateProcess()在这种情况下,根据文档,它可以完全省略第一个参数值:

lpApplicationName参数可以为 NULL。在这种情况下,模块名称必须是字符串中第一个以空格分隔的标记lpCommandLine

var
  StartupInfo: TSTARTUPINFO;
  ProcessInfo: PROCESS_INFORMATION;
  ...
  CmdLine: String;
begin
  Result := 0;
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := SizeOf(StartupInfo);
  ...
  CmdLine := TrimRight(AnsiQuotedStr(sExe, '"') + ' ' + sParam);
  ...    
  if CreateProcess(nil, PChar(CmdLine), ...) then
于 2015-08-26T21:19:12.393 回答