Przejęcie outputu polecenia Windows i jego dalsze działanie po zamknięciu aplikacji wywołującej.

0

Cześć.

Zwykle sobie radzę sam, ale tutaj słaba znajomość obsługi procesów i potoków mnie przerosła. Dlatego proszę o pomoc z przykładami kodów źródłowych. Jak wiadomo taki kod:

function ExecNewProcess(ProgramName : string; CmdShow : longword) : DWORD;
var
  SI : TStartupInfo;
  PI : TProcessInformation;
begin
  ZeroMemory(@SI, SizeOf(SI));
  SI.cb := SizeOf(SI);
  SI.dwFlags := STARTF_USESHOWWINDOW;
  SI.wShowWindow := CmdShow;
  CreateProcess(nil, PChar(ProgramName), nil, nil, False, 0, nil, nil, SI, PI);
  Result := PI.dwProcessId;
end;

Bez problemów wywoła nam polecenie z parametrami i zwórci PID wywołanego procesu, jeśli się on otworzy. A nawet jeśli zamkniemy program, który użył tego kodu, a inny wywołany proces nadal pracuje, to będzie to trwało nadal bez problemów.

Jednak ja chciałbym wywołać ukryte okno procesu, którym będzie rtmpdump.exe przekierowany na VLC. Wszystko działa. Jednak zamknięcie programu, który go wywołał po sukcesywnym wywołaniu, powoduje że po jakimś czasie stream RTMP, który przekierowuje rtmpdump.exe zapętla się. Tak jakby nie otrzymywał już nowych danych.

Jak widzicie zakomentowałem wszelkie CloseHandle. Korzystałem zamiennie z tych kodów, które nieznacznie zmodyfikowałem. Kody są wygooglowane. A zależy na wychwyceniu mi zwróconego komunikatu przez rtmpdump.exe, który następuje góra po kilku sekundach czasu od jego uruchomienia.

procedure CaptureConsoleOutput(const ACommand : string; AMemo : TMemo);
const
  CReadBuffer = 2400;
var
  DRead, DRunning : DWORD;
  SaSecurity : TSecurityAttributes;
  HRead, HWrite : THandle;
  suiStartup : TStartupInfo;
  piProcess : TProcessInformation;
  pBuffer : array[0..CReadBuffer] of Char;
begin
  SaSecurity.nLength := SizeOf(TSecurityAttributes);
  SaSecurity.bInheritHandle := True;
  SaSecurity.lpSecurityDescriptor := nil;
  if CreatePipe(hRead, hWrite, @SaSecurity, 0) then
  begin
    FillChar(suiStartup, SizeOf(TStartupInfo), #0);
    suiStartup.cb := SizeOf(TStartupInfo);
    suiStartup.hStdInput := hRead;
    suiStartup.hStdOutput := hWrite;
    suiStartup.hStdError := hWrite;
    suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    suiStartup.wShowWindow := SW_HIDE;
    if CreateProcess(nil, PChar(ACommand), @SaSecurity,
      @SaSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then
    begin
      repeat
        dRunning := WaitForSingleObject(piProcess.hProcess, 100);
        Application.ProcessMessages();
        repeat
          dRead := 0;
          ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
          pBuffer[dRead] := #0;
          OemToAnsi(pBuffer, pBuffer);
          AMemo.Lines.Add(string(pBuffer));
        until (dRead < CReadBuffer);
      until (dRunning <> WAIT_TIMEOUT);
      //CloseHandle(piProcess.hProcess);
      //CloseHandle(piProcess.hThread);
    end;
    //CloseHandle(hRead);
    //CloseHandle(hWrite);
  end;
end;

function GetDosOutput(CommandLine : string) : string;
var
  SA : TSecurityAttributes;
  SI : TStartupInfo;
  PI : TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite : THandle;
  WasOK : Boolean;
  Buffer : array[0..255] of AnsiChar;
  BytesRead : Cardinal;
  Handle : Boolean;
begin
  Result := '';
  with SA do
  begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  try
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;
    Handle := CreateProcess(nil, PChar(CommandLine),
      nil, nil, True, 0, nil,
      'C:\', SI, PI);
    CloseHandle(StdOutPipeWrite);
    if Handle then
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            Result := Result + Buffer;
          end;
          if Length(Result) > 1000 then
          begin
            Windows.Beep(444, 555);
            Break;
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        //CloseHandle(PI.hThread);
        //CloseHandle(PI.hProcess);
      end;
  finally
    //CloseHandle(StdOutPipeRead);
  end;
end;

Jak można je poprawić? Czy w ogóle możliwe jest odebranie części zwróconych tekstów przez program konsolowy w ukrytym oknie, a następnie pozwolić mu działać nawet jeśli zamknie się program, który go wywołał?

Dodatkowe pytanie. Jeśli ktoś wie. Ewentualnie jak można odebrać własną aplikacją delphi potok początkowych komunikatów przed wysłanych danych, by później mogły dalej iść do VLC jak po użyciu przekierowania potoku przez parametr -o-| "C:\costam\sciezka\do\vlc\vlc.exe". Dodam, że niestety rtmpdump.exe nie pozwala na zwrócenie wyników po wywołaniu z >> costam.txt. Bo wtedy próbował bym z lamerskim zapisem pliku do %TEMP%. Odczytem i usunięciem pliku tymczasowego. Ale taki plik ma zero bajtów.

Z góry dzięki za przykłady kodów i pomoc. Sorry za rozpisanie się jak to zwykle u mnie :)

1

Dodatkowe pytanie. Jeśli ktoś wie. Ewentualnie jak można odebrać własną aplikacją delphi potok początkowych komunikatów przed wysłanych danych, by później mogły dalej iść do VLC jak po użyciu przekierowania potoku przez parametr -o-| "C:\costam\sciezka\do\vlc\vlc.exe". Dodam, że niestety rtmpdump.exe nie pozwala na zwrócenie wyników po wywołaniu z >> costam.txt. Bo wtedy próbował bym z lamerskim zapisem pliku do %TEMP%. Odczytem i usunięciem pliku tymczasowego. Ale taki plik ma zero bajtów.

Z tego co wiem to rtmpdump swoje komunikaty wysyła na stderr. Więc albo 2> plik gdy przekierowujemy stderr w poleceniu albo STARTUPINFO.hStdError w CreateProcess.

0

Hmmm, dodanie 2> plik dalej tworzy pusty 0 bajtowy plik. Chyba, że to dlatego, że akurat nie było błędów w testowym poleceniu zapisu do pliku flv, bo się powiódł. A jak z tym pierwszym pytaniem ma ktoś jakieś pomysły?

1

EDIT: odświeżam ten wątek. Ponieważ googlując za przykładami, skleciłem taki kod jak poniżej. Działa on dokładnie tak, jak chciałem. Program - w moim przypadku rtmpdump.exe wywołuje się i faktycznie zapodaje wszystko na StdErr. Funkcja zwraca też PID wywołanego procesu.

Oczywiście zwraca go niemal Od razu, jeżeli wartość bajtów do odczytwania jaką podamy, jest rzędu kilkuset. Jeżeli podamy 0 to program odczytuje tak długo, dopóki jest to możliwe. Także najlepiej uruchamiać to w osobnym wątku. I tym razem dostępność "procedury - zdarzenia" odczytana do zmiennej boolean, żeby się nikt tutaj już nie musiał tego czepiać ;P

Dzięki parametrowi CmdShow, możemy podać SW_HIDE, jeśli okno procesu ma być ukryte. Warto je jednak tak ukrywac ponieważ i tak często wszystko już "odbera" nasz program. Przez co okno konslowe będzie bez pojawiąjącego się nowego tekstu. Oczywiście da się to co zrobiłem poniżej pewnie uzyskać inaczej i może lepiej. Ale dla mnie jest ok, I działa pod WinAPI. Najważniejsze, że wywołany proces po zamnięciu naszego programu nadal działa bez problemów.

Także swoją odpowiedź zatwierdzam. Mając nadzieją, że nie tylko mnie się taki kod przyda :)

//...

type
  TOnGetStdErrProc = procedure(ErrLine : string);

function ExecAndGetStdErr(FullCommand : string; CmdShow : LongWord;
  OnGetStdErrProc : TOnGetStdErrProc; HowManyBytesRead : DWORD) : DWORD;
const
  PipeSecurityAttributes : TSecurityAttributes = (
    nLength : SizeOf(TSecurityAttributes);
    bInheriTHandle : True
    );
var
  OnGetStdSet : boolean;
  StartupInfo : TStartupInfo;
  HStdErrR, HStdErrW : THandle;
  ProcessInfo : TProcessInformation;
  ErrBytesRead, TotalBytesRead : DWORD;
  ErrBuffer : array[0..4096 - 1] of Char;
begin
  OnGetStdSet := @OnGetStdErrProc <> nil;
  CreatePipe(HStdErrR, HStdErrW, @PipeSecurityAttributes, 0);
  try
    SeTHandleInformation(HStdErrR, HANDLE_FLAG_INHERIT, 0);
    ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
    StartupInfo.cb := SizeOf(StartupInfo);
    StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo.wShowWindow := CmdShow;
    StartupInfo.hStdError := HStdErrW;
    CreateProcess(nil, PChar(FullCommand), nil, nil,
      True, 0, nil, nil, StartupInfo, ProcessInfo);
    Result := ProcessInfo.dwProcessId;
    TotalBytesRead := 0;
    if OnGetStdSet then
    begin
      repeat
        ReadFile(HStdErrR, ErrBuffer, SizeOf(ErrBuffer), ErrBytesRead, nil);
        TotalBytesRead := TotalBytesRead + ErrBytesRead;
        OnGetStdErrProc(ErrBuffer);
        if HowManyBytesRead > 0 then
        begin
          if TotalBytesRead > HowManyBytesRead then
          begin
            Break;
          end;
        end;
      until ErrBytesRead = 0;
    end;
  finally
    CloseHandle(HStdErrR);
    if HStdErrW <> 0 then
    begin
      CloseHandle(HStdErrW);
    end;
  end;
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1