WinExec()的可接受替换?

时间:2015-09-26 22:31:10

标签: delphi winapi

我想运行用户在INI文件中定义的命令。

命令可以是EXE文件,也可以是其他文件(例如DOC文件)和参数。

由于WinExec()可以处理参数(例如" cmd /?"),但ShellExec()可以处理非EXE文件(例如" Letter.doc"),I我正在使用这两者的组合。

我担心未来的Windows版本,因为WinExec()已被弃用,甚至是16位时代。

这是我目前的职能:

procedure RunCMD(cmdLine: string; WindowMode: integer);

  procedure ShowWindowsErrorMessage(r: integer);
  begin
    MessageDlg(SysErrorMessage(r), mtError, [mbOK], 0);
  end;

var
  r, g: Cardinal;
begin
  // We need a function which does following:
  // 1. Replace the Environment strings, e.g. %SystemRoot%  --> ExpandEnvStr
  // 2. Runs EXE files with parameters (e.g. "cmd.exe /?")  --> WinExec
  // 3. Runs EXE files without path (e.g. "calc.exe")       --> WinExec
  // 4. Runs EXE files without extension (e.g. "calc")      --> WinExec
  // 5. Runs non-EXE files (e.g. "Letter.doc")              --> ShellExecute
  // 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes. 

  cmdLine := ExpandEnvStr(cmdLine);

  // TODO: Attention: WinExec() is deprecated, but there is no acceptable replacement
  g := WinExec(PChar(cmdLine), WindowMode);
  r := GetLastError;
  if g = ERROR_BAD_FORMAT then
  begin
    // e.g. if the user tries to open a non-EXE file
    ShellExecute(0, nil, PChar(cmdLine), '', '', WindowMode);
    r := GetLastError;
  end;
  if r <> 0 then ShowWindowsErrorMessage(r);
end;

function ExpandEnvStr(const szInput: string): string;
// http://stackoverflow.com/a/2833147/3544341
const
  MAXSIZE = 32768;
begin
  SetLength(Result, MAXSIZE);
  SetLength(Result, ExpandEnvironmentStrings(pchar(szInput),
    @Result[1],length(Result)));
end;

Microsoft建议使用CreateProcess(),但我不接受它作为WinExec()的真正替代品。

例如,给出以下命令行:

"C:\Program Files\xyz.exe" /a /b /c

由于ShellExecute()和CreateProcess()需要严格分离命令和参数,我必须自己解析这个字符串。这真的是我唯一能走的路吗?是否有人编写了具有此功能的公共可用代码?

附加说明:该过程不应附加到呼叫者。命令启动后,我的程序将关闭。

2 个答案:

答案 0 :(得分:4)

CreateProcess()WinExec()的替代品。文档明确说明了这一点。

而BTW,原始代码中的错误处理是完全错误的。你在滥用GetLastError()。实际上,WinExec()ShellExecute()都不会报告GetLastError()开头的错误。因此,即使WinExec()ShellExecute()成功(并且您甚至不检查ShellExecute()是成功还是失败),您也有可能报告早期API调用中的随机错误。

尝试更像这样的事情:

procedure RunCMD(cmdLine: string; WindowMode: integer);

  procedure ShowWindowsErrorMessage(r: integer);
  var
    sMsg: string;
  begin
    sMsg := SysErrorMessage(r);
    if (sMsg = '') and (r = ERROR_BAD_EXE_FORMAT) then
      sMsg := SysErrorMessage(ERROR_BAD_FORMAT);
    MessageDlg(sMsg, mtError, [mbOK], 0);
  end;

var
  si: TStartupInfo;
  pi: TProcessInformation;
  sei: TShellExecuteInfo;
  err: Integer;
begin
  // We need a function which does following:
  // 1. Replace the Environment strings, e.g. %SystemRoot%  --> ExpandEnvStr
  // 2. Runs EXE files with parameters (e.g. "cmd.exe /?")  --> WinExec
  // 3. Runs EXE files without path (e.g. "calc.exe")       --> WinExec
  // 4. Runs EXE files without extension (e.g. "calc")      --> WinExec
  // 5. Runs non-EXE files (e.g. "Letter.doc")              --> ShellExecute
  // 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes. 

  cmdLine := ExpandEnvStr(cmdLine);
  {$IFDEF UNICODE}
  UniqueString(cmdLine);
  {$ENDIF}

  ZeroMemory(@si, sizeof(si));
  si.cb := sizeof(si);
  si.dwFlags := STARTF_USESHOWWINDOW;
  si.wShowWindow := WindowMode;

  if CreateProcess(nil, PChar(cmdLine), nil, nil, False, 0, nil, nil, si, pi) then
  begin
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    Exit;
  end;

  err := GetLastError;
  if (err = ERROR_BAD_EXE_FORMAT) or
     (err = ERROR_BAD_FORMAT) then
  begin
    ZeroMemory(@sei, sizeof(sei));
    sei.cbSize := sizeof(sei);
    sei.lpFile := PChar(cmdLine);
    sei.nShow := WindowMode;

    if ShellExecuteEx(@sei) then Exit;
    err := GetLastError;
  end;

  ShowWindowsErrorMessage(err);
end;

答案 1 :(得分:2)

虽然类似的ShellExecuteCreateProcess有不同的用途。

ShellExecute可以打开不可执行的文件。它在注册表中查找给定文件的关联程序的信息并执行它。 ShellExecute也非常适合启动默认Web浏览器,您可以将URL传递给它。

因此,如果您要将TXT文件传递给ShellExecute,它将打开相关程序,例如记事本。但是它会因CreateProcess而失败。

CreateProcess是一个较低级别的函数,它允许您更好地控制进程的输入和输出。例如,您可以使用CreateProcess调用具有文本输出的命令行程序,并捕获该输出并作出反应。

考虑到您的担忧,您需要使用ShellExecute。但是,您需要从参数中拆分命令。这将是第一个非转义的空白字符。

我个人很少直接调用ShellExecute或CreateProcess。我倾向于使用包含这些函数的JCL中的以下函数。

JclMiscel.pas

  • CreateDosProcessRedirected
  • WinExec32
  • WinExec32AndWait
  • WinExecAndRedirectOutput
  • CreateProcessAsUser
  • CreateProcessAsUserEx

JclShell.pas

  • ShellExecEx
  • ShellExec
  • ShellExecAndWwait
  • RunAsAdmin

JclSysUtils.pas

  • 执行(8个重载版本,是我最常用的版本)