Мониторинг консольного вывода без перехвата

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Мониторинг консольного вывода без перехвата

Сообщение Alex2013 » 24.08.2023 12:26:16

Вообщем столкнулся с интересной проблемой :
Есть методы перехвата консольного ввода-вывода и есть методы инкапсуляции реальной консоли.

Первые отлично работают, но полностью адекватного эмулятора терминала я так и не нашел и то что я вижу в "эмуляторе терминала" часто совершенно не похоже на то что видно в реальной консоле.

Вторые могут нормально отображать вывод консольной программы но я совершенно не имею доступа выводу, а его желательно как-то парсить .

Отсюда вопрос: как добраться до консольного "тестового буфера" и прочитать то что в нем находится или просто дублировать захваченный вывод обратно в консоль :?:
Захват консоли
Код: Выделить всё
Procedure RunDosInMemo(CmdLine: String; AMemo: TMemo);
Const
    ReadBuffer = 1023;
Var
    Security: TSecurityAttributes;
    OutReadPipe, OutWritePipe: tHandle; // труба для output'a консольной проги.
    InReadPipe, InWritePipe: tHandle; // труба для input'a консольной проги.
    ErrReadPipe, ErrWritePipe: tHandle; // труба для error's консольной проги.
    // InReadPipe, ErrReadPipe и объявлены для полноты картины,но не создаются и не используются.
    start: TStartUpInfo;
    ProcessInfo: TProcessInformation;
    Buffer: Pchar;
    BytesRead: DWord;
    Apprunning: DWord;
    avail : dword;
    notread:dword;
    stop:boolean;
Begin
    stop := false;
    With Security Do Begin // инициализация структуры
        nlength := SizeOf(TSecurityAttributes);
        binherithandle := true;
        lpsecuritydescriptor := Nil;
    End;
    Createpipe(InReadPipe, InWritePipe, @Security, 0);
    Createpipe(ErrReadPipe, ErrWritePipe, @Security, 0);
    If Createpipe(OutReadPipe, OutWritePipe, @Security, 0) Then Begin
        // создали трубу для выхлопа бэкграунд-приложения
        Buffer := AllocMem(ReadBuffer + 1);
        // создали буфер для чтения
        FillChar(Start, Sizeof(Start), #0);
        // заполнили содержимое стартовой структуры #0
        start.cb := SizeOf(start);
        start.hStdOutput := OutWritePipe;
        start.hStdError := OutWritePipe;
        start.hStdInput := InReadPipe;
        (*************************************************************
            такой себе опширненьний комментарий...
            Оказывается, мать их так, если сделать перенаправление
            вывода в трубы, но не читать его, то если он(вывод)
            будет достаточно длинный и сможет переполнить буфер,
            который изначально отводится под трубу, то пишущий поток
            остановится и будет ждать пока не освободится место в
            буфере трубы. Как только оно освободилось, он сможет
            продолжать работу и писать дальше.

            start.hStdOutput := OutWritePipe;
            start.hStdError := OutWritePipe;

            почему собственно такой странный код: два потока
            перенаправлены в одну трубу?
            Потому что некоторые замечательные проги типа 7zip свой
            вывод направляют не в StdOut, а почему то в StdErr...
            и если для этих двух потоков назначить две разных трубы,
            а читать только одну, то произойдет то, что описано выше.
            РРРРРРРРРРРРРРРРРРРРРРРРР!!!!!!!!! сопли, слюни, ярость и
            буйное помешательство на почве программирования под винду.

            Может стоит сделать две трубы и читать каждую в отдельное
            мемо???
        **************************************************************)
        start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
        start.wShowWindow := SW_HIDE;
        // окно прячем

        If CreateProcess(Nil, PChar(CmdLine), @Security, @Security, true, NORMAL_PRIORITY_CLASS,
            Nil, Nil, start, ProcessInfo) Then Begin
            // создали процесс
            Repeat
                Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
                PeekNamedPipe(OutReadPipe, @Buffer[0], ReadBuffer, @BytesRead, @avail, @notread);
                // PeekNamedPipe копирует из буфера трубы и оставляет его в первоначальном состоянии
                // в то время как ReadFile читая из трубы - опустошает ее.
                // PeekNamedPipe можно использовать для того чтобы узнать сколько данных есть в трубе
                // и если в PeekNamedPipe передать 2 и 3 параметры пустыми, то она просто скажет
                // сколько данных есть в трубе
                if avail > 0 then begin
                    ReadFile(OutReadPipe, Buffer[0], BytesRead, BytesRead, Nil); // *******
                    // ReadFile при чтении из трубы опустошает ее(трубы) буфер.
                end
                else begin
                    if Apprunning <> 258 then
                        stop := true;
                end;
                // читаем через читающий конец трубы из вывода консоли
                Buffer[BytesRead] := #0;
                // последний символ #0 - конец буфера
                OemToAnsi(Buffer, Buffer);
                // перевели из кодировки DOS в кодировку WIN
                AMemo.Text := AMemo.text + String(Buffer);
                // то что прочитали приписали к тексту в мемо
                Application.ProcessMessages;
                // обработали очередь сообщений
//            Until ((Apprunning <> WAIT_TIMEOUT) or (avail < 0));
            Until stop;
            // прервемся когда процесс завершится
        End;
        FreeMem(Buffer); // освободили буфер
        CloseHandle(ProcessInfo.hProcess); // закрыли все хендлы
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(OutReadPipe);
        CloseHandle(OutWritePipe);
        CloseHandle(InReadPipe);
        CloseHandle(InWritePipe);
        CloseHandle(ErrReadPipe);
        CloseHandle(ErrWritePipe);
    End;
    // конец.
End;


Инкапсуляция консоли
Код: Выделить всё


Const  Con_h:hwnd =0;
       SearchHandle:hwnd =0;

function EnumProc(h:HWND; lParam:DWord):boolean; stdcall;
var
ProcessId, z:cardinal;
begin
Result:=True;
GetWindowThreadProcessId(h, ProcessId);
If ProcessId=lParam then
begin
SearchHandle:=h;
Result:=False;
end;
end;

procedure TForm1.Run_IN_RealConsle;
var
ExecInfo: TShellExecuteInfoA;
buf:array[1..100] of char;
S:String;
P:Pointer;
Var
   StartTime:QWord;
begin
..
  ZeroMemory(@si, sizeof(si));
  si.cb:=SizeOf(si);
  P:=@EnumProc;

  CreateProcess(nil, 'cmd /C TmpListCmd.Bat', nil, nil, false, 0, nil, nil, @si,@pi);
  SearchHandle:=0;
  sleep(150);
  StartTime:=GetTickCount64;

  While (SearchHandle=0) and ( GetTickCount64-StartTime<2000) do
  begin
    Windows.EnumWindows(@EnumProc,Lparam( pi.dwProcessId) );
   Application.ProcessMessages;
  end;

if SearchHandle<>0 then  begin
   Con_H:=SearchHandle;
...
   windows.SetWindowLong(Con_H, GWL_STYLE, WS_VISIBLE + WS_POPUP);
   windows.SetParent(Con_h,panel11.Handle);
   windows.MoveWindow(Con_h,0,0,panel11.Width,panel11.Height,true);
end;
end;


То есть мне нужен некий гибрид обоих методов работы с консолью (захватить без скрытия и дублировать вывод обратно в консоль которую в свою очередь можно инкапсулировать ) или дополнительная надстройка над "инкапсуляцией" ( с периодическим чтением тестового буфера консоли ).

Добавлено спустя 36 минут 39 секунд:
Зы
Что-то такое нашел но как-то это пока что криво у меня выходит. ( + Еще бы событие на обновление найти бо отслеживать по таймеру не очень надежно )
https://delphisources.ru/forum/showthread.php?t=7520

Добавлено спустя 12 минут 28 секунд:
Бр голова распухает ...
https://question-it.com/questions/67219 ... stochnikov
Последний раз редактировалось Alex2013 24.08.2023 17:42:26, всего редактировалось 1 раз.
Alex2013
долгожитель
 
Сообщения: 3048
Зарегистрирован: 03.04.2013 11:59:44

Re: Мониторинг консольного вывода без перехвата

Сообщение xchgeaxeax » 24.08.2023 14:56:09

Alex2013 писал(а):Отсюда вопрос: как добраться до консольного "тестового буфера" и прочитать то что в нем находится или просто дублировать захваченный вывод обратно в консоль :?:
Захват консоли

Пробовали выделить собственную консоль (AllocConsole) и выводить не в TMemo, а в нее. Что получается? Такая же как и в TMemo разница.
xchgeaxeax
постоялец
 
Сообщения: 125
Зарегистрирован: 11.05.2023 03:51:40

Re: Мониторинг консольного вывода без перехвата

Сообщение Alex2013 » 24.08.2023 17:39:27

Спасибо за отзыв. Попробую, но сейчас я через ReadConsoleOutputAttribute читать пытаюсь .
(Это без захвата, цвета меня особо не интересуют в общем посмотрю что получится... )
Alex2013
долгожитель
 
Сообщения: 3048
Зарегистрирован: 03.04.2013 11:59:44

Re: Мониторинг консольного вывода без перехвата

Сообщение Vapaamies » 24.08.2023 23:22:13

В Windows 10 появился же терминал (ConPTY).
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 292
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Мониторинг консольного вывода без перехвата

Сообщение Alex2013 » 26.08.2023 22:41:54

Vapaamies писал(а):В Windows 10 появился же терминал (ConPTY).

Это хорошо но мне совместимость (минимум с 7-й ) нужна ( Для мини проекта Shell2Shell совместимость не особо важна (сомневаюсь что браузерный движок WebView2 заработает в Win 7 ) однако "Мониторинг консольного вывода " нужен не только там хотя и в Shell2Shell желательно поддерживать работу хотя-бы на всех версиях 10-ки )

Добавлено спустя 14 часов 59 минут 43 секунды:
В страшных муках творчества что-то все-же заработало.
Изображение



Тестовый код (гибрид из кучи примеров и моих личных домыслов ) ...

Код: Выделить всё
procedure TMainForm.Button5Click(Sender: TObject);
const
   _WIN32_WINNT = $0501;

type
   TAttachConsole = function (dwProcessId: DWORD): LongBOOL stdcall;

var
  AttachConsole: TAttachConsole;
  mProcessID, Wnd, Hcwnd, chRead: Cardinal;
  BufInfo: _CONSOLE_SCREEN_BUFFER_INFO;
  lpCh: PChar;
  Coord: _COORD;
  i: Integer;
   S,S1:String;
   J:Integer;
begin
if windows.IsWindow(con_h)
    then begin

  @AttachConsole:= GetProcAddress(GetModuleHandle('kernel32.dll'), 'AttachConsole');

  if @AttachConsole = nil then
  begin
    MessageBox(0, 'Программа работает только под Windows XP и выше!', 'Error', 16);
    exit;
  end;

Wnd:= con_h; // con_h хемлб консольного окна

  GetWindowThreadProcessId(Wnd, @mProcessID);
  if AttachConsole(mProcessID) then begin
    Hcwnd:= GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(Hcwnd, BufInfo);

     S:=''; S1:='';
     for j:=0 to BufInfo.dwMaximumWindowSize.X+1 do S1:=S1+' ';
      S:=BufInfo.dwSize.Y.ToString+#13+#10;
      for i:=BufInfo.srWindow.Top to BufInfo.srWindow.Bottom-1 {BufInfo.dwSize.Y-1} do

        begin
         Coord.X := 0;
         Coord.Y := i;
       if  not ReadConsoleOutputCharacter(Hcwnd, @S1[1],
          BufInfo.dwMaximumWindowSize.X
       , Coord, chRead) then  break;
        if  chRead =0 then break;
         S:=S+I.ToString+' '+ S1+
         ' '+Coord.X.ToString+' '+Coord.y.ToString+' '+chRead.ToString+' ' +#13+#10;
      end;
      OEM866ToUTF8(S);
      ShowMessage('['+S+']');
  end else ShowMessage('Error  Attach Console');
  FreeConsole ;

end;

end;



Суть в "моментальном" захвате текста из окна "инкапсулированной" консоли .
Нуждается в доработке (медленно и много лишнего но по идее можно отслеживать непосредственно строчку с курсором ).
Наверное самый "долгоотлаживаемый" код во всем проекте. УФ ! (Даже про завтрак забыл ! :wink: )
Alex2013
долгожитель
 
Сообщения: 3048
Зарегистрирован: 03.04.2013 11:59:44

Re: Мониторинг консольного вывода без перехвата

Сообщение Alex2013 » 31.08.2023 13:24:34

Прикрутил мониторинг к проекту Shell2Shell "полет нормальный" (мониторит все окно терминала (морока со строчкой под куросом себя не оправдала), а чтобы ничего не путалось, пока что сделал "однократный последовательный поиск").
Изображение
Alex2013
долгожитель
 
Сообщения: 3048
Зарегистрирован: 03.04.2013 11:59:44


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 13

Рейтинг@Mail.ru