【问题标题】:Delphi 7 - ShellExecute command not working in situationsDelphi 7 - ShellExecute 命令在某些情况下不起作用
【发布时间】:2014-12-02 12:53:58
【问题描述】:

我制作了一个游戏启动器,我使用这个命令:

 procedure TFMain.ImgBtn1Click(Sender: TObject);
     begin
      ShellExecute(TForm(Owner).Handle, nil, 'starter.exe', '-lang rus', nil, SW_SHOWNORMAL);
     end;

使用“-lang rus”作为参数。一切正常。游戏启动,语言为俄语(如果我输入“-lang eng”,它仍然可以正常工作,并且游戏是英文的)。

starter.exe 应用程序位于名为“bin”的文件夹中。当我想将启动器重新定位到此文件夹之外时,我使用此命令:

procedure TFMain.ImgBtn1Click(Sender: TObject);
     begin
      ShellExecute(TForm(Owner).Handle, nil, 'bin\starter.exe', '-lang rus', nil, SW_SHOWNORMAL);
     end;

然后游戏没有启动。实际上什么也没发生。 我应该改变什么?

【问题讨论】:

  • 您应该使用完全限定的路径。
  • 你能说得更具体些吗?
  • 尝试将lpDirectory参数中的可执行文件目录传递给ShellExecute
  • 1. TForm(Owner).Handle 充其量看起来很可疑。在任何情况下都不需要它。通过0。 2.使用CreateProcess创建进程。当您需要让 shell 确定如何解析 shell 动词时,请使用 ShellExecuteEx。 3. 经常检查错误。我无法想象你为什么忽略了这一点。也就是说,ShellExecute 不会以理智的方式报告错误,你永远不应该使用它。如果您必须解析 shell 动词,请使用 ShellExecuteEx,它确实有正确的错误报告。
  • 您是否尝试在命令行上使用starter.exe -lang rus 来查找潜在的错误消息?

标签: delphi shellexecute


【解决方案1】:

您必须使用您尝试启动的应用程序的完整路径。

ExtractFilePath(Application.ExeName) 将为您提供启动器 exe 的完整路径。

解决方案一:使用ShellExecute

procedure TFMain.ImgBtn1Click(Sender: TObject);
var 
  ExecuteResult: integer;
  Path: string;
begin
  Path := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));
  ExecuteResult := ShellExecute(0, nil, PChar(Path + 'bin\starter.exe'), '-lang rus', nil, SW_SHOWNORMAL);
  if ExecuteResult <= 32 then ShowMessage('Error: ' + IntToStr(ExecuteResult));
end;

您可以在以下位置找到错误代码列表:ShellExecute function documentation

最常见的错误代码:

  • ERROR_FILE_NOT_FOUND 0x2
  • ERROR_PATH_NOT_FOUND 0x3

解决方案 2: 使用 ShellExecuteEx

var
  FileName, Parameters, Folder: string;
  sei: TShellExecuteInfo;
  Error: DWORD;
  OK: boolean;
begin
  Folder := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) + 'bin\';
  FileName := Folder + 'starter.exe';
  Parameters := '-lang rus';
  ZeroMemory(@sei, SizeOf(sei));
  sei.cbSize := SizeOf(sei);
  sei.lpFile := PChar(FileName);
  sei.lpParameters := PChar(Parameters);
  sei.lpDirectory := PChar(Folder);
  sei.nShow := SW_SHOWNORMAL;
  OK := ShellExecuteEx(@sei);
  if not OK then
    begin
      Error := GetLastError;
      ShowMessage('Error: ' + IntToStr(Error));
    end;
end;

ShellExecuteEx documentation

解决方案 3: 使用CreateProcess

function ExecuteProcess(const FileName, Params: string; Folder: string; WaitUntilTerminated, WaitUntilIdle, RunMinimized: boolean;
  var ErrorCode: integer): boolean;
var
  CmdLine: string;
  WorkingDirP: pchar;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  Result := true;
  CmdLine := '"' + FileName + '" ' + Params;
  if Folder = '' then Folder := ExcludeTrailingPathDelimiter(ExtractFilePath(FileName));
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := SizeOf(StartupInfo);
  if RunMinimized then
    begin
      StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
      StartupInfo.wShowWindow := SW_SHOWMINIMIZED;
    end;
  if Folder <> '' then WorkingDirP := pchar(Folder)
  else WorkingDirP := nil;
  if not CreateProcess(nil, pchar(CmdLine), nil, nil, false, 0, nil, WorkingDirP, StartupInfo, ProcessInfo) then
    begin
      Result := false;
      ErrorCode := GetLastError;
      exit;
    end;
  with ProcessInfo do
    begin
      CloseHandle(hThread);
      if WaitUntilIdle then WaitForInputIdle(hProcess, INFINITE);
      if WaitUntilTerminated then
        repeat
          Application.ProcessMessages;
        until MsgWaitForMultipleObjects(1, hProcess, false, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 + 1;
      CloseHandle(hProcess);
    end;
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  FileName, Parameters, Folder: string;
  Error: integer;
  OK: boolean;
begin
  Folder := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) + 'bin\';
  FileName := Folder + 'starter.exe';
  Parameters := '-lang rus';
  OK := ExecuteProcess(FileName, Parameters, Folder, false, false, false, Error);
  if not OK then
    begin
      Error := GetLastError;
      ShowMessage('Error: ' + IntToStr(Error));
    end;
end;

CreateProcess documentation

【讨论】:

  • 其实PChar 适用于所有Delphi版本
  • ExtractFilePath() 通常包含尾部斜杠,因此不要在 'bin\starter' 中包含前导斜杠。最好使用IncludeTrailingPathDelimiter(ExtractFilePath(...)),以解决ExtractFilePath() 不包含斜杠的极端情况。
  • 您链接到一组不相关的错误代码。这些不适用于 ShellExecute。这导致您应该永远调用 ShellExecute。
  • 试图坚持这一点毫无意义。编写不处理错误的代码是没有意义的。停止提倡 ShellExecute。永远不要调用它。 ShellExecuteEx 是你的人。但是,在这种情况下,需要 CreateProcess。
  • @David 我也添加了 CreateProcess。我们可以稍后删除 cmets。
【解决方案2】:

您应该使用完全限定(绝对)路径。例如,如果你知道路径是

C:\Program Files (x86)\My Company\My Game\bin\starter.exe

你应该传递那个字符串。当然,您应该从不硬编码这样的字符串,因为它在不同的系统上可能会有所不同。如果您的应用程序是通用应用程序启动器,您会从用户那里获得路径。如果您的应用程序启动了您自己公司的游戏,您必须想出一种巧妙的方式来传达路径。

从你的问题看不清楚,但如果bin\starter.exe 是相对于你的应用程序的路径,你可以使用

ExtractFilePath(Application.ExeName) + 'bin\starter.exe'

顺便说一句,您可以通过查看ShellExecute 的返回值自己弄清楚这一切。当然,你已经仔细阅读了ShellExecutedocumentation,所以你知道返回值是什么。因此,您很容易识别出ERROR_FILE_NOT_FOUND 并意识到您需要一个完全合格的路径。

【讨论】:

  • 没有任何“找不到文件”消息。它只是没有任何反应。我也不能有完整的路径 bc ''C:\Program Files (x86)\XXXXXXX\My Game\bin\starter.exe'' 每台 PC 的 xxx 都不同。如果我使用我在问题中编写的代码而没有参数 -lang rus- 即使我使用以下命令将启动器放在“bin”文件夹之外,应用程序也会正常启动:ShellExecute(TForm(Owner).Handle, nil, 'bin\starter.exe', nil, nil, SW_SHOWNORMAL);
  • 不,没有消息框。但我也没有这么说。 “返回值”是函数返回的值。另外,我确实解决了寻找绝对路径的问题。我认为您应该通过在运行时构建的绝对路径。无论如何,返回值是什么?您评论的最后一部分表明该问题非常具体到 starter.exe(因此不适合 StackOverflow)。
  • @user3211668 你没有检查错误所以你期望什么?
  • @AndreasRejbrand 实际上,ShellExecute 在报告错误时基本上是无用的。你需要ShellExecuteEx
  • @user3211668 这是个坏主意。启动您的流程非常容易。为什么会这么轻易放弃?
猜你喜欢
  • 1970-01-01
  • 2012-12-19
  • 2018-11-28
  • 2011-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-17
相关资源
最近更新 更多