-
Всем привет! Задача: запускать из gui приложения различные консольные программы, в том числе батники, с возможностью посмотреть, что они выводят на консоль, и в то же время записью всего этого в файл.
Что пробовал: 1) Перенаправление input и output на неименованные пайпы. Всё прекрасно, но вывод прог типа wget вообще не ловится. Также у многих прог наблюдается задержка вывода в связи с буферизацией printf
2) Использование собственной консоли, создаваемой через AllocConsole. Тоже неплохо, сделал, чтобы она пряталась и показывалась, с закрытием проги при закрытии консоли тоже можно смириться, но! Никак не пойму, как прочитать оттуда выводимую информацию, чтобы сохранять ее в файл. Пробовал делать ReadFile(GetStdHandle(STD_OUTPUT_HANDLE)), но не сработало. Видимо, считывать инфу можно только через буфер, посредством ReadConsoleOutputCharacter? Но тогда возникают некоторые заморочки с синхронизацией (как определить, до какой позиции считывать, когда очищать, вдруг за это время запущенная прога еще что-то напишет и т.п.)
На крайний случай есть вариант сделать прогу-заглушку, но он мне не нравится. Есть у кого-нибудь идеи, как можно решить данную проблему?
-
Как мне кажется, проблему синхронизации можно решить, тормозя поток дочерней программы на время считывания буфера. Не очень надежное решение, но лучше повышения приоритета родительского процесса до критического, которое предлагают вот здесь http://www.codeproject.com/KB/threads/RTconsole.aspx
-
> 1) Перенаправление input и output на неименованные пайпы
еще можно stderror перенаправлять
-
Это понятно, хотя я не думаю, что именно туда пишется основная инфа, выводимая программой.
Ну так что по поводу вопроса? Кто-то может что-нибудь посоветовать?
-
Никак не пойму, как прочитать оттуда выводимую информацию, чтобы сохранять ее в файл. Пробовал делать ReadFile(GetStdHandle(STD_OUTPUT_HANDLE)), но не сработало.
Создаем консоль. Полученный хендл передаем в параметрах CreateProcess. Дальше читаем оттуда.
-
> [4] Медвежонок Пятачок © (18.06.09 11:25)
Читаем откуда? h := GetStdHandle(STD_OUTPUT_HANDLE); si.hStdOutput := h CreateProcess(...) ReadFile(h, buf, 1024, bytesread, nil); вот так?
-
Причем здесь GetStdHandle?
Ты же консоль явно создаешь.
-
Тогда я не понимаю, к чему относятся слова > Полученный хендл
-
сначала создаешь устройства ввода/вывода для стартуемого консольного процесса. таким образом хендлы у тебя есть. затем делаешь createprocess, передавая хендлы новому процессу. после этого его инпут-аутпут операции будут работать с переданными хендлами.
-
держи unit ConsoleRedirect;
interface
uses
Classes, Windows, SyncObjs;
type
TConsoleRedirector = class(TThread)
private
FErrorCode: Integer;
FCmdPath: string;
FSendCmd: AnsiString;
FOutput: AnsiString;
FLocker: TCriticalSection;
procedure OutputLines(const AText: AnsiString);
protected
procedure Execute; override;
public
constructor Create(); reintroduce; overload;
destructor Destroy; override;
procedure SendBuff(const AText: AnsiString);
property Terminated;
property Locker: TCriticalSection read FLocker write FLocker;
property SendCmd: AnsiString read FSendCmd write FSendCmd;
property Output: AnsiString read FOutput write FOutput;
end;
implementation
uses
SysUtils;
const
CRLF = #13#10;
constructor TConsoleRedirector.Create;
var
pch: PChar;
begin
inherited Create(True);
FLocker := TCriticalSection.Create;
GetMem(pch, MAX_PATH);
try
GetSystemDirectory(pch, MAX_PATH);
FCmdPath := pch;
UniqueString(FCmdPath);
finally
FreeMem(pch);
end;
FCmdPath := IncludeTrailingPathDelimiter(FCmdPath) + 'cmd.exe';
end;
destructor TConsoleRedirector.Destroy;
begin
FLocker.Free;
inherited Destroy;
end;
procedure TConsoleRedirector.Execute;
const
BufSize = 512;
var
Buf: array[0..BufSize - 1] of AnsiChar;
sInternalBuff: AnsiString;
si: TStartupInfo;
sa: SECURITY_ATTRIBUTES;
pi: TProcessInformation;
hNewStdIn, hNewStdOut, hReadStdOut, hWriteStdIn: THandle;
bread, avail, cExitCode: Cardinal;
begin
FErrorCode := 0;
if Win32Platform = VER_PLATFORM_WIN32_NT then
begin
sa.nLength := SizeOf(SECURITY_ATTRIBUTES);
sa.bInheritHandle := True;
sa.lpSecurityDescriptor := nil;
end
else
begin
sa.lpSecurityDescriptor := nil;
end;
hNewStdIn := 0;
hNewStdOut := 0;
hReadStdOut := 0;
hWriteStdIn := 0;
FillChar(si, SizeOf(TStartupInfo), 0);
FillChar(pi, SizeOf(TProcessInformation), 0);
try
if not CreatePipe(hNewStdIn, hWriteStdIn, @sa, 0) then
begin
FErrorCode := 1;
Exit;
end;
SetHandleInformation(hWriteStdIn, HANDLE_FLAG_INHERIT, 0);
if not CreatePipe(hReadStdOut, hNewStdOut, @sa, 0) then
begin
FErrorCode := 1;
Exit;
end;
SetHandleInformation(hReadStdOut, HANDLE_FLAG_INHERIT, 0);
si.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
si.wShowWindow := SW_HIDE;
si.hStdOutput := hNewStdOut;
si.hStdError := hNewStdOut;
si.hStdInput := hNewStdIn;
if not CreateProcess(PChar(FCmdPath), nil, nil, nil, True,
CREATE_NEW_CONSOLE, nil, nil, si, pi) then
begin
FErrorCode := 2;
Exit;
end;
CloseHandle(pi.hThread);
FillChar(Buf, SizeOf(Buf), 0);
while not Terminated do
begin
Sleep(10);
GetExitCodeProcess(pi.hProcess, cExitCode);
if cExitCode <> STILL_ACTIVE then
begin
FErrorCode := 3;
Exit;
end;
PeekNamedPipe(hReadStdOut, @Buf, BufSize, @bread, @avail, nil);
while bread <> 0 do
begin
FillChar(Buf, BufSize, 0);
if avail > BufSize then
begin
while bread >= bufsize do
begin
ReadFile(hReadStdOut, Buf, BufSize, bread, nil);
sInternalBuff := sInternalBuff + Buf;
FillChar(Buf, BufSize, 0);
end;
end
else
begin
ReadFile(hReadStdOut, Buf, BufSize, bread, nil);
sInternalBuff := sInternalBuff + Buf;
end;
PeekNamedPipe(hReadStdOut, @Buf, BufSize, @bread, @avail, nil);
end;
FLocker.Enter;
try
if sInternalBuff <> '' then
begin
OutputLines(sInternalBuff);
sInternalBuff := '';
end;
sInternalBuff := FSendCmd;
while Length(sInternalBuff) > 0 do
begin
WriteFile(hWriteStdIn, sInternalBuff[1], 1, bread, nil);
Delete(sInternalBuff, 1, 1);
end;
sInternalBuff := '';
FSendCmd := '';
finally
FLocker.Leave;
end;
end;
finally
if hNewStdIn <> 0 then
CloseHandle(hNewStdIn);
if hNewStdOut <> 0 then
CloseHandle(hNewStdOut);
if hReadStdOut <> 0 then
CloseHandle(hReadStdOut);
if hWriteStdIn <> 0 then
CloseHandle(hWriteStdIn);
if pi.hProcess <> 0 then
begin
TerminateProcess(pi.hProcess, 0);
end;
if pi.hProcess <> 0 then
CloseHandle(pi.hProcess);
end;
end;
procedure TConsoleRedirector.OutputLines(const AText: AnsiString);
var
sText: AnsiString;
begin
sText := AText;
sText := AnsiString(StringReplace(string(sText), #13#13#10, #13, [rfReplaceAll]));
FOutput := FOutput + sText;
end;
procedure TConsoleRedirector.SendBuff(const AText: AnsiString);
begin
FLocker.Enter;
try
FSendCmd := FSendCmd + AText + CRLF;
finally
FLocker.Leave;
end;
end;
end.
-
> [8] Медвежонок Пятачок © (18.06.09 13:02)
Извини, я не понимаю, можешь на пальцах?
> [9] Eraser © (18.06.09 13:04)
Не запускает батник... пишет Microsoft Windows XP [Версия 5.1.2600](С) Корпорация Майкрософт, 1985-2001.
D:\Coding\Projects\Delphi\!Test\Test> и все
-
а еще, зачем SetHandleInformation(hWriteStdIn, HANDLE_FLAG_INHERIT, 0); ? Неужели признака в TSecurityAttributes недостаточно?
-
Получилось-таки!!! Сделал вот так:
var hReadPipe: THandle = 0;
hWritePipe: THandle = 0;
hProc: THandle = 0;
buf: array [0..1023] of char;
procedure TForm1.Button2Click(Sender: TObject);
var si: TStartupInfo;
pi: TProcessInformation;
sa: TSecurityAttributes;
dir: string;
bytesread: cardinal;
h: thandle;
space: integer;
begin
FillChar(sa, SizeOf(sa), 0);
sa.nLength := SizeOf(sa);
sa.lpSecurityDescriptor := nil;
sa.bInheritHandle := True;
if not CreatePipe(hReadPipe, hWritePipe, @sa, 0) then
Error(LastErrMsg);
FillChar(si, SizeOf(si), 0);
FillChar(pi, SizeOf(pi), 0);
si.cb := SizeOf(si);
si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
si.wShowWindow := SW_HIDE;
si.hStdOutput := hWritePipe;
si.hStdError := hWritePipe;
space := Pos(' ',edit4.Text);
if space = 0 then space := Length(edit4.Text)+1;
dir := ExtractFilePath(Copy(edit4.Text,1,space-1));
if dir = '' then dir := ExtractFilePath(Application.ExeName);
if not CreateProcess(nil,PChar(edit4.Text),@sa,nil,True,0,nil,PChar(dir),si,pi)
then Error(LastErrMsg)
else memo2.Lines.Add('>process launched');
hProc := pi.hProcess;
CloseHandle(pi.hThread);
timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var bytesread, cExitcode: cardinal;
begin
if (hProc = 0) or (hWritePipe = 0) or (hReadPipe = 0) then Exit;
if (not GetExitCodeProcess(hProc, cExitcode)) or (cExitcode <> STILL_ACTIVE)
then begin CloseHandle(hProc); hProc := 0; end;
repeat
PeekNamedPipe(hReadPipe, nil, 0, nil, @bytesread, nil);
if bytesread = 0 then
begin
if hProc = 0 then CloseHandle(hReadPipe);
Break;
end;
FillChar(buf, SizeOf(buf), 0);
if not ReadFile(hReadPipe, buf, 1024, bytesread, nil) then
begin Timer1.Enabled := False; Error(LastErrMsg); end;
OemToAnsi(buf,buf);
if bytesread > 0 then
memo2.Text:=memo2.Text+(pchar(@buf));
sendmessage(memo2.Handle, WM_VSCROLL, SB_BOTTOM, 0);
until bytesread = 0;
if hProc = 0 then
memo2.Lines.Add('>process exited');
end; Понял, почему хрень была... я не очень понимал суть труб и считывал из конца для записи, того же, который подсовывал дочернему процессу. Плюс оказалось, что wget зачем-то отправляет свой лог на STDERR.
-
Удалено модератором Примечание: Не в пивной
|