카테고리 없음

[시스템] DOS 명령어 실행하고 실시간 결과 받아오기

쇼핑스크래퍼2 2023. 8. 30. 08:28
// DOS 명령어 실행하고 결과 받아오는 일반적인 예제들은 DOS 명령어의 실행이
// 끝난 후 화면 출력을 보여주지만 아래 예제는 DOS 명령어를 실행하고 발생하는
// 화면 출력을 바로바로 출력하는 실시간성 캡쳐 예제입니다

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure GetDosOutput(cmd: String; OutputMemo: TMemo);
const
  BufSize = $4000;

type
  TPipeHandles = record
    hRead,
    hWrite: DWORD;
  end;

  procedure ClosePipe(var Pipe: TPipeHandles);
  begin
    with Pipe do
    begin
      if hRead <> 0 then CloseHandle (hRead);
      if hWrite <> 0 then CloseHandle (hWrite);
      hRead := 0;
      hWrite := 0;
    end;
  end;

  procedure ReadPipe(var Pipe: TPipeHandles);
  var
    ReadBuf: array[0..BufSize] of Char;
    BytesRead: Dword;
  begin
    // 파이프에 읽을 데이터가 있는지 검사
    if PeekNamedPipe(Pipe.hRead, nil, 0, nil, @BytesRead, nil) and
      (BytesRead > 0) then // 읽을 데이터가 있다
    begin
      ReadFile(Pipe.hRead, ReadBuf, BytesRead, BytesRead, nil);
      if BytesRead > 0 then
      begin
        ReadBuf[BytesRead] := #0;
        OutputMemo.SelText := ReadBuf;
        OutputMemo.Perform(EM_SCROLLCARET, 0, 0);
      end;
    end;
  end;

var
  ProcessInfo: TProcessInformation;
  StartupInfo: TStartupInfo;
  SecAttr: TSecurityAttributes;
  PipeStdOut: TPipeHandles;
  PipeStdErr: TPipeHandles;
  dwExitCode: DWORD;

begin
  SecAttr.nLength := SizeOf(SecAttr);
  SecAttr.lpSecurityDescriptor := nil;
  SecAttr.bInheritHandle := TRUE;

  with PipeStdOut do // 표준 출력(stdout) 파이프
    if not CreatePipe (hRead, hWrite, @SecAttr, BufSize) then
      ShowMessage('STDOUT pipe 를 만들 수 없습니다');

  try
    with PipeStdErr do // 표준 에러(stderr) 파이프
      if not CreatePipe (hRead, hWrite, @SecAttr, BufSize) then
        ShowMessage('STDERR pipe 를 만들 수 없습니다');
  except
    ClosePipe(PipeStdOut);
    raise;
    exit;
  end;

  try
    FillChar(StartupInfo,SizeOf(StartupInfo), 0);
    with StartupInfo do
    begin
      cb          := SizeOf(StartupInfo);
      dwFlags     := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      hStdOutput  := PipeStdOut.hWrite;
      hStdError   := PipeStdErr.hWrite;
      wShowWindow := SW_HIDE;
    end;

    if CreateProcess(
        nil, PChar(Cmd), @SecAttr, @SecAttr, true,
        DETACHED_PROCESS or NORMAL_PRIORITY_CLASS,
        nil, nil,
        StartupInfo, ProcessInfo) then
    begin
      dwExitCode := STILL_ACTIVE;
      Screen.Cursor := crHourglass;
      try
        repeat
          // 두번쨰 파라미터가 0이면 즉시 리턴
          WaitForSingleObject(ProcessInfo.hProcess, 0);
          GetExitCodeProcess(ProcessInfo.hProcess, dwExitCode);

          Application.ProcessMessages;

          ReadPipe(PipeStdOut);
          ReadPipe(PipeStdErr);
        until dwExitCode <> STILL_ACTIVE; // 아직 실행중이면 반복

        if not GetExitCodeProcess(ProcessInfo.hProcess, dwExitCode) then
         ShowMessage('exit code 를 읽어올 수 없습니다');

        if dwExitCode <> 0 then // 정상 종료가 아니면...
          raise Exception.Create('Exit code ' + IntToStr(dwExitCode));

      finally
        Screen.Cursor := crDefault;
        if dwExitCode = STILL_ACTIVE then
          TerminateProcess(ProcessInfo.hProcess, 1);
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
        ProcessInfo.hProcess := 0;
      end;
    end
    else
      ShowMessage(Cmd+' 명령어 실행을 위한 프로세스 생성 실패');

  finally
   ClosePipe(PipeStdOut);
   ClosePipe(PipeStdErr);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear;
  GetDosOutput(Edit1.Text, Memo1);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // 실행할 DOS 명령어
  Edit1.Text := 'ping www.howto.pe.kr';
end;

end.