为什么当应用程序由 IDE 与启动器启动时,命令行参数的数量会发生变化?

Why the quantity of command-line arguments changes when the application is started by the IDE vs an launcher?

考虑以下命令行参数

"alfa" "beta" "4"

当我为我正在工作的项目指定 运行>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就是可执行文件的路径。 为什么会这样?

注意:我已经设计了如何更改命令行参数解释以针对这种边缘情况保持健壮 - 这里的重点为什么会发生这种情况。

您的启动器程序未正确调用 CreateProcess。考虑摘自 the documentation 的这段摘录(强调已添加):

If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.

忽略"C programmers";它适用于为 Windows 编写程序的每个人,无论使用何种语言。

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

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

If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.

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

启动器不包括 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() 文档,它可以完全省略第一个参数值:

The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space–delimited token in the lpCommandLine string.

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