delphi 通过ShellExecute()执行命令行应用程序并获取其返回值

ttvkxqim  于 2022-11-23  发布在  Shell
关注(0)|答案(2)|浏览(874)

我是我们公司的一名 Delphi 开发人员。我们需要一个函数来启动一个命令行可执行文件并获取其返回值。
我写的代码和我在网上找到的所有例子都是通过CreateProcess()来实现的,但是我的老板拒绝了,告诉我一定有一个解决方案是通过ShellExecute()来实现的。我在网上找不到任何使用ShellExecute()的例子。所有的例子都使用CreateProcess()
下面是我交给老板的3个方法。他不喜欢ShellExecute_AndGetReturnValue()。它被命名为“ShellExecute”,但它没有使用ShellExecute()
这三种方法都很好用,但第一种方法不是使用ShellExecute(),而是使用CreateProcess()
那么,是否有可能解决/改变ShellExecute_AndGetReturnValue()方法,使其使用ShellExecute()而不是CreateProcess()?我找到的所有示例,所有示例都使用CreateProcess()

function ShellExecute_AndGetReturnValue(FileName : string; Params : string = ''; Show : Integer = SW_HIDE; WorkingDir : string = '') : string;
const
  READ_BUFFER_SIZE = 2048;
var
  Security: TSecurityAttributes;
  readableEndOfPipe, writeableEndOfPipe, readableErrorEndOfPipe, writeableErrorEndOfPipe: THandle;
  start: TStartUpInfo;
  ProcessInfo: TProcessInformation;
  Buffer: PAnsiChar;
  BytesRead: DWORD;
  AppRunning: DWORD;
  ResultStdOutput : string;
  ResultErrOutput : string;
  lpDirectory : PAnsiChar;
  CmdLine : string;
begin
  Result := '';
  Security.nLength := SizeOf(TSecurityAttributes);
  Security.bInheritHandle := True;
  Security.lpSecurityDescriptor := nil;

  if CreatePipe(readableEndOfPipe, writeableEndOfPipe, @Security, 0) then
  begin
    Buffer := AllocMem(READ_BUFFER_SIZE + 1);
    FillChar(Start, Sizeof(Start), #0);
    FillChar(ProcessInfo, SizeOf(ProcessInfo), #0);

    start.cb := SizeOf(start);
    start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;
    start.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
    start.hStdOutput := writeableEndOfPipe;

    CreatePipe(readableErrorEndOfPipe, writeableErrorEndOfPipe, @Security, 0);
    start.hStdError := writeableErrorEndOfPipe;
    start.hStdError := writeableEndOfPipe;
    start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;
    start.wShowWindow := Show;

    UniqueString(FileName);
    CmdLine := '"' + FileName + '" ' + Params;

    if WorkingDir <> '' then
    begin
      lpDirectory := PAnsiChar(WorkingDir);
    end else
    begin
      lpDirectory := PAnsiChar(ExtractFilePath(FileName));
    end;

    if CreateProcess(nil, PChar(CmdLine), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, lpDirectory, start, ProcessInfo) then
    begin
      repeat
          Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
          Application.ProcessMessages;
      until (Apprunning <> WAIT_TIMEOUT);

      ResultStdOutput := '';
      ResultErrOutput := '';

      //Must Close write Handles before reading (if the console application does not output anything)
      CloseHandle(writeableEndOfPipe);
      CloseHandle(writeableErrorEndOfPipe);

      repeat
        BytesRead := 0;
        ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
        Buffer[BytesRead]:= #0;
        OemToAnsi(Buffer,Buffer);
        ResultStdOutput := ResultStdOutput + String(Buffer);
      until (BytesRead < READ_BUFFER_SIZE);

      if start.hStdOutput <> start.hStdError then
      begin
        BytesRead := 0;
        ReadFile(readableErrorEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
        Buffer[BytesRead]:= #0;
        OemToAnsi(Buffer,Buffer);
        ResultErrOutput := ResultErrOutput + String(Buffer);
      end;
    end;

    Result := ResultStdOutput;

    FreeMem(Buffer);
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(readableEndOfPipe);
    CloseHandle(readableErrorEndOfPipe);
  end;
end;

procedure ShellExecute_NoWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
  exInfo: TShellExecuteInfo;
  Ph: DWORD;
begin
  FillChar(exInfo, SizeOf(exInfo), 0);
  with exInfo do
  begin
    cbSize := SizeOf(exInfo);
    fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
    Wnd := GetActiveWindow();
    lpVerb := PAnsiChar(Action);
    lpParameters := PChar(Params);
    lpFile := PChar(FileName);
    nShow := Show;
    if WorkingDir <> '' then
    begin
      lpDirectory := PAnsiChar(WorkingDir);
    end else
    begin
      lpDirectory := PAnsiChar(ExtractFilePath(FileName));
    end;
  end;
  if ShellExecuteEx(@exInfo) then
  begin
    Ph := exInfo.HProcess;
    CloseHandle(Ph);
  end;
end;

procedure ShellExecute_AndWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
  exInfo: TShellExecuteInfo;
  Ph: DWORD;
begin
  FillChar(exInfo, SizeOf(exInfo), 0);
  with exInfo do
  begin
    cbSize := SizeOf(exInfo);
    fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
    Wnd := GetActiveWindow();
    lpVerb := PAnsiChar(Action);
    lpParameters := PChar(Params);
    lpFile := PChar(FileName);
    nShow := Show;
    if WorkingDir <> '' then
    begin
      lpDirectory := PAnsiChar(WorkingDir);
    end else
    begin
      lpDirectory := PAnsiChar(ExtractFilePath(FileName));
    end;
  end;
  if ShellExecuteEx(@exInfo) then
  begin
    Ph := exInfo.HProcess;
    while WaitForSingleObject(ExInfo.hProcess, 50) <> WAIT_OBJECT_0 do
    begin
      Application.ProcessMessages;
    end;
    CloseHandle(Ph);
  end;
end;
q9yhzks0

q9yhzks01#

你老板的任务不完全正确。问题是ShellExecute的通用解决方案-不是startcmd.exe,这个命令启动一个链接到这种类型文件的应用程序并启动它。所以,让它像你想要的那样工作-它需要大量的工作。还有一件事-你需要得到你的程序的工作结果还是你的程序的控制台输出?下面是从jcl库中修改部分源代码返回的返回代码:

function PCharOrNil(const S: string): PChar;
begin
  Result := Pointer(S);
end;

// memory initialization
procedure ResetMemory(out P; Size: Longint);
begin
  if Size > 0 then
  begin
    Byte(P) := 0;
    FillChar(P, Size, 0);
  end;
end;

function ShellExecAndWait(const FileName: string; const Parameters: string;
  const Verb: string; CmdShow: Integer; const Directory: string): cardinal;
var
  Sei: TShellExecuteInfo;
  Res: LongBool;
  Msg: tagMSG;
  ShellResult : boolean;
begin
  ResetMemory(Sei, SizeOf(Sei));
  Sei.cbSize := SizeOf(Sei);
  Sei.fMask := SEE_MASK_DOENVSUBST  or SEE_MASK_FLAG_NO_UI  or SEE_MASK_NOCLOSEPROCESS or
    SEE_MASK_FLAG_DDEWAIT or SEE_MASK_NOASYNC;
  Sei.lpFile := PChar(FileName);
  Sei.lpParameters := PCharOrNil(Parameters);
  Sei.lpVerb := PCharOrNil(Verb);
  Sei.nShow := CmdShow;
  Sei.lpDirectory := PCharOrNil(Directory);
  {$TYPEDADDRESS ON}
  ShellResult := ShellExecuteEx(@Sei);
  {$IFNDEF TYPEDADDRESS_ON}
  {$TYPEDADDRESS OFF}
  {$ENDIF ~TYPEDADDRESS_ON}
  if ShellResult then begin
    WaitForInputIdle(Sei.hProcess, INFINITE);

    while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
      repeat
        Msg.hwnd := 0;
        Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
        if Res then
        begin
          TranslateMessage(Msg);
          DispatchMessage(Msg);
        end;
      until not Res;

    if not GetExitCodeProcess(Sei.hProcess, Result) then
      raise Exception.Create('GetExitCodeProcess fail');

    CloseHandle(Sei.hProcess);
  end else begin
    raise Exception.Create('ShellExecuteEx fail');
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  xResult : cardinal;
begin
  xResult := ShellExecAndWait('ping.exe', '', '', 1, '');  //xResult = 1
  xResult := ShellExecAndWait('ping.exe', '8.8.8.8', '', 1, '');  //xResult = 0
end;
31moq8wy

31moq8wy2#

如果您需要指定输入/输出管道(以控制被调用进程的stdin和stdout),则不能使用ShellExecute。它只是不支持指定这些管道。ShellExecuteEx也不支持。
因此,如果你必须使用ShellExecute,你唯一的选择就是ShellExecute命令处理器(CMD.EXE),并要求它执行输入和输出的重定向。这将把你的重定向源和目标限制在磁盘上的物理文件上,因为这是CMD.EXE允许重定向的方式(〉StdOut〈StdIn)。
另外,您使用CreateProcess的方法是前进的方向。您的老板给予了什么理由让您 * 必须 * 使用ShellExecute?
如果您 * 不 * 需要重定向支持,则可以使用ShellExecuteEx,然后在成功执行之后,您可以在Info.hProcess(Info是传递给ShellExecuteEx的TShellExecuteInfo结构)中获取正在运行的进程的句柄。
然后,可以在GetExitCodeProcess中使用该值来确定进程是否仍在运行,或者它是否已终止(如果我正确理解了您对该表达式的使用,则您已检索到“返回值”-它实际上称为“ExitCode”,或者在批处理文件中称为“ERRORLEVEL”)。
不完整的代码:

FUNCTION ShellExecuteAndWait(....) : DWORD;
.
.
VAR Info : TShellExecuteInfo;
.
.
Info.fMask:=Info.fMask OR SEE_MASK_NOCLOSEPROCESS;
IF NOT ShellExecuteEx(Info) THEN EXIT($FFFF8000);
IF Info.hProcess=0 THEN EXIT($FFFF0000);
REPEAT
  IF NOT GetExitCodeProcess(Info.hProcess,Result) THEN EXIT($FFFFFFFF)
UNTIL Result<>STILL_ACTIVE
.
.

上面的代码应该演示如何做到这一点...

相关问题