首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >通过ShellExecute()执行命令行应用程序并获取其返回值

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

Stack Overflow用户
提问于 2022-06-13 10:13:06
回答 2查看 648关注 0票数 1

我是我们公司的Delphi开发人员。我们需要一个函数来启动命令行可执行文件并获得它的返回值。

我编写的代码,以及我在互联网上找到的所有例子,都是通过CreateProcess()实现的,但我的老板拒绝了这一点,并告诉我必须有一个通过ShellExecute()解决这个问题的解决方案。我在互联网上找不到用ShellExecute()做这件事的例子。他们都使用CreateProcess()

下面是我给老板的三种方法。他不喜欢ShellExecute_AndGetReturnValue()。它被命名为"ShellExecute",但它不使用ShellExecute()

这三种方法都很好。但第一个问题是不使用ShellExecute()。相反,它正在使用CreateProcess()

那么,是否有可能解决/更改ShellExecute_AndGetReturnValue()方法,使其使用ShellExecute()而不是CreateProcess()?我找到的所有示例都使用CreateProcess()

代码语言:javascript
运行
复制
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;
EN

回答 2

Stack Overflow用户

发布于 2022-06-14 04:42:38

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

代码语言:javascript
运行
复制
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;
票数 2
EN

Stack Overflow用户

发布于 2022-06-13 10:23:03

如果需要指定输入/输出管道(以控制被调用进程的stdin和stdout ),则不能使用ShellExecute。它根本不支持指定这些。ShellExecuteEx也是如此。

因此,如果必须使用ShellExecute,唯一的选择是ShellExecute命令处理器(CMD.EXE),并要求它执行输入和输出的重定向。这将限制您的重定向源和目标到磁盘上的物理文件,因为这是CMD.EXE允许重定向的方式(>StdOut

不然的话,您使用CreateProcess的方法就是前进的方向。你的老板给你什么理由让你必须使用ShellExecute?

如果不需要重定向支持,可以使用ShellExecuteEx,然后在成功执行之后,可以在Info.hProcess中获得正在运行的进程的句柄(Info是传递给ShellExecuteEx的TShellExecuteInfo结构)。

然后,可以在GetExitCodeProcess中使用该值来确定进程是否仍在运行,或者它是否已终止(因此,您已经检索了“返回值”,如果我正确理解了这个表达式的用法-它实际上被称为"ExitCode",或者--在批处理文件中称为"ERRORLEVEL")。

不完整代码:

代码语言:javascript
运行
复制
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
.
.

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72601144

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档