在delphi中如何获取dos窗口的输出?(100分)

  • 主题发起人 liwen_big
  • 开始时间
L

liwen_big

Unregistered / Unconfirmed
GUEST, unregistred user!
比方说,现在有一个用c语言编写的,已经编译好的可执行文件myout.exe,<br>它的作用是在屏幕上输出'hello world!'。在delphi程序中调用这个可执行文件,<br>如何把它所输出内容'helloworld!'取到并保存在一个memo中呢?
 
主要是用pipe,<br>演示程序已上传到:http://www.playicq.com/dispdoc.asp?id=1066<br><br>unit ConsoleUnit;<br>{30/4/00<br>This unit demonstrates a GUI application spawning a<br>console application, and capturing the output of the<br>console application to display in the GUI application.<br><br>Any matters arising, questions, comments etc... contact<br><br>Martin Lafferty<br>martinl@prel.co.uk<br><br>Production Robots Engineering Ltd<br>Box 2290, Wimborne, Dorset, BH21 2YY, England.<br><br>Background<br>----------<br>This example is based on a similar thing I wrote some years<br>ago which worked not very well under Win95 and not at all under<br>Windows NT. If you are one of the many people who wrote to me<br>asking me about this, I am sorry it has taken me so long to sort<br>it out. I didn't have a need for it until now, and I have been<br>busy - you know how it is.<br><br>The Win32 SDK has a topic called<br><br>"Creating a Child process with redirected input and output". I tried to<br>use that as a basis for this work but found it very confusing and could<br>not really get it to do what I wanted. The code presented here is really<br>based on information from Richter ("Advanced Windows" ISBN 1-57231-548-2)<br>notably chapters 2 (Kernel Objects) and chapter 3 (Processes)<br><br>Here is an interesting thing that might be bug (but I don't think so)<br><br>Try this on NT:<br>Open TestApp.dpr (simple console app, supplied) and compile<br>Open ConsoleTest.dpr in the Delphi IDE<br>Enter TestApp as command line.<br>You should get an output - testapp should return 0.<br><br>Now without closing down Delphi close ConsoleTest.dpr and reopen TestApp.dpr.<br>Try to compile and you will get a 'Cannot create output file' error - which<br>normally indicates that the EXE image is still loaded, but if you check the<br>process list using the NT Task manager there is no sign of Testapp.exe.<br><br>If you close Delphi, and restart it, you can compile OK.<br><br><br>It would be reasonable to assume that a bug in ConsoleTest.dpr was failing to<br>allow TestApp to terminate properly. I have looked for such a bug, and cannot<br>find anything. If you run ConsoleTest direct from NT (not in the IDE) then the<br>problem is not present. You can compile TestApp.dpr quite happily in the IDE<br>after running the EXE via ConsoleTest running outside the IDE. I am not too<br>sure what is going on here but it seems to be only a problem when TestApp is<br>running as a grandchild of Delphi. If you find out more, let me know.<br>}<br><br><br>interface<br><br>uses<br>&nbsp; Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,<br>&nbsp; StdCtrls, ExtCtrls;<br><br>type<br>&nbsp; TConsoleForm = class(TForm)<br>&nbsp; &nbsp; Output: TListBox;<br>&nbsp; &nbsp; Panel1: TPanel;<br>&nbsp; &nbsp; CmdLineLabel: TLabel;<br>&nbsp; &nbsp; CmdLineEdit: TEdit;<br>&nbsp; &nbsp; BrowseButton: TButton;<br>&nbsp; &nbsp; RunButton: TButton;<br>&nbsp; &nbsp; BrowseDlg: TOpenDialog;<br>&nbsp; &nbsp; procedure RunButtonClick(Sender: TObject);<br>&nbsp; &nbsp; procedure BrowseButtonClick(Sender: TObject);<br>&nbsp; private<br>&nbsp; &nbsp; procedure RunningUpdate(Sender: TObject);<br>&nbsp; public<br>&nbsp; &nbsp; { Public declarations }<br>&nbsp; end;<br><br>var<br>&nbsp; ConsoleForm: TConsoleForm;<br><br>implementation<br><br>{$IFDEF DEBUG}<br>var<br>&nbsp; ReadCount: Integer;<br>{$ENDIF}<br><br>function ExecConsoleApp(CommandLine: String;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AppOutput: TStrings; &nbsp; &nbsp; {will receive output of child process}<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; OnNewLine: TNotifyEvent &nbsp;{if assigned called on each new line}<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ): Cardinal;<br><br>{child process has no input. I have not thought about this.<br>Function returns exit code of child process (normally 0 for no error)<br><br>If the function returns STILL_ACTIVE ($00000103) then the ReadLoop<br>has terminated before the app has finished executing. See comments in body<br>of function<br>}<br><br>const<br>&nbsp; CR = #$0D;<br>&nbsp; LF = #$0A;<br>&nbsp; TerminationWaitTime = 5000;<br><br>var<br>&nbsp; StartupInfo:TStartupInfo;<br>&nbsp; ProcessInfo:TProcessInformation;<br>&nbsp; SecurityAttributes: TSecurityAttributes;<br><br>&nbsp; TempHandle,<br>&nbsp; WriteHandle,<br>&nbsp; ReadHandle: THandle;<br>&nbsp; ReadBuf: array[0..$100] of Char;<br>&nbsp; BytesRead: Cardinal;<br>&nbsp; LineBuf: array[0..$100] of Char;<br>&nbsp; LineBufPtr: Integer;<br>&nbsp; Newline: Boolean;<br>&nbsp; i: Integer;<br><br>procedure OutputLine;<br>begin<br>&nbsp; LineBuf[LineBufPtr]:= #0;<br>&nbsp; with AppOutput do<br>&nbsp; if Newline then<br>&nbsp; &nbsp; Add(LineBuf)<br>&nbsp; else<br>&nbsp; &nbsp; Strings[Count-1]:= LineBuf; {should never happen with count = 0}<br>&nbsp; Newline:= false;<br>&nbsp; LineBufPtr:= 0;<br>&nbsp; if Assigned(OnNewLine) then<br>&nbsp; &nbsp; OnNewLine(AppOutput) &nbsp;{there is no reasonable justification for passing<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;AppOutput as self, but I don't have anything else}<br>end;<br><br>begin<br>&nbsp; FillChar(StartupInfo,SizeOf(StartupInfo), 0);<br>&nbsp; FillChar(ReadBuf, SizeOf(ReadBuf), 0);<br>&nbsp; FillChar(SecurityAttributes, SizeOf(SecurityAttributes), 0);<br>{$IFDEF DEBUG}<br>&nbsp; ReadCount:= 0;<br>{$ENDIF}<br>&nbsp; LineBufPtr:= 0;<br>&nbsp; Newline:= true;<br>&nbsp; with SecurityAttributes do<br>&nbsp; begin<br>&nbsp; &nbsp; nLength:= Sizeof(SecurityAttributes);<br>&nbsp; &nbsp; bInheritHandle:= true<br>&nbsp; end;<br>&nbsp; if not CreatePipe(ReadHandle, WriteHandle, @SecurityAttributes, 0) then<br>&nbsp; &nbsp; RaiseLastWin32Error;<br>&nbsp; {create a pipe to act as StdOut for the child. The write end will need<br>&nbsp; &nbsp;to be inherited by the child process}<br><br>&nbsp; try<br>&nbsp; &nbsp; {Read end should not be inherited by child process}<br>&nbsp; &nbsp; if Win32Platform = VER_PLATFORM_WIN32_NT then<br>&nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; if not SetHandleInformation(ReadHandle, HANDLE_FLAG_INHERIT, 0) then<br>&nbsp; &nbsp; &nbsp; &nbsp; RaiseLastWin32Error<br>&nbsp; &nbsp; end else<br>&nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; {SetHandleInformation does not work under Window95, so we<br>&nbsp; &nbsp; &nbsp; have to make a copy then close the original}<br>&nbsp; &nbsp; &nbsp; if not DuplicateHandle(GetCurrentProcess, ReadHandle,<br>&nbsp; &nbsp; &nbsp; &nbsp; GetCurrentProcess, @TempHandle, 0, True, DUPLICATE_SAME_ACCESS) then<br>&nbsp; &nbsp; &nbsp; &nbsp; RaiseLastWin32Error;<br>&nbsp; &nbsp; &nbsp; CloseHandle(ReadHandle);<br>&nbsp; &nbsp; &nbsp; ReadHandle:= TempHandle<br>&nbsp; &nbsp; end;<br><br>&nbsp; &nbsp; with StartupInfo do<br>&nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; cb:= SizeOf(StartupInfo);<br>&nbsp; &nbsp; &nbsp; dwFlags:= STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;<br>&nbsp; &nbsp; &nbsp; wShowWindow:= SW_HIDE;<br>&nbsp; &nbsp; &nbsp; hStdOutput:= WriteHandle<br>&nbsp; &nbsp; end;<br>&nbsp; &nbsp; {Initialise the startup info. I suspect that it is only safe to pass<br>&nbsp; &nbsp; WriteHandle as hStdOutput because we are going to make sure that the<br>&nbsp; &nbsp; child inherits it. This is not documented anywhere, but I am reasonably<br>&nbsp; &nbsp; sure it is correct. We should not have to use STARTF_USESHOWWINDOW and<br>&nbsp; &nbsp; wShowWindow:= SW_HIDE as we are going to tell CreateProcess not to<br>&nbsp; &nbsp; bother with an output window, but it would appear that Windows 95<br>&nbsp; &nbsp; ignores the CREATE_NO_WINDOW flag. Fair enough - it is not in the SDK<br>&nbsp; &nbsp; documentation (I got it out of Richter). CREATE_NO_WINDOW definately works<br>&nbsp; &nbsp; under NT 4.0, so it is worth doing}<br><br>&nbsp; &nbsp; if not CreateProcess(nil, PChar(CommandLine), nil, nil,<br>&nbsp; &nbsp; &nbsp; &nbsp;true, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {inherit kernel object handles from parent}<br>&nbsp; &nbsp; &nbsp; &nbsp;NORMAL_PRIORITY_CLASS or CREATE_NO_WINDOW,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{DETACHED_PROCESS relevant for Console parent only<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;No need to create an output window - it would be<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;blank anyway}<br>&nbsp; &nbsp; &nbsp; &nbsp;nil,<br>&nbsp; &nbsp; &nbsp; &nbsp;nil,<br>&nbsp; &nbsp; &nbsp; &nbsp;StartupInfo,<br>&nbsp; &nbsp; &nbsp; &nbsp;ProcessInfo) then<br>&nbsp; &nbsp; &nbsp;RaiseLastWin32Error;<br><br>&nbsp; &nbsp; CloseHandle(ProcessInfo.hThread);<br>&nbsp; &nbsp; {not interested in threadhandle - close it}<br><br>&nbsp; &nbsp; CloseHandle(WriteHandle);<br>&nbsp; &nbsp; {close our copy of Write handle - Child has its own copy now. It is important<br>&nbsp; &nbsp; to close ours, otherwise ReadFile may not return when child closes its<br>&nbsp; &nbsp; StdOutput - this is the mechanism by which the following loop detects the<br>&nbsp; &nbsp; termination of the child process: it does not poll GetExitCodeProcess.<br><br>&nbsp; &nbsp; The clue to this behaviour is in the 'Anonymous Pipes' topic of Win32.hlp - quote<br><br>&nbsp; &nbsp; "To read from the pipe, a process uses the read handle in a call to the<br>&nbsp; &nbsp; ReadFile function. When a write operation of any number of bytes completes,<br>&nbsp; &nbsp; the ReadFile call returns. The ReadFile call also returns when all handles<br>&nbsp; &nbsp; to the write end of the pipe have been closed or if any errors occur before<br>&nbsp; &nbsp; the read operation completes normally."<br><br>&nbsp; &nbsp; On this basis (and going somewhat beyond that stated above) I have assumed that<br>&nbsp; &nbsp; ReadFile will return TRUE when a write is completed at the other end of the pipe<br>&nbsp; &nbsp; and will return FALSE when the write handle is closed at the other end.<br><br>&nbsp; &nbsp; I have also assumed that ReadFile will return when its output buffer is full<br>&nbsp; &nbsp; regardless of the size of the write at the other end.<br><br>&nbsp; &nbsp; I have tested all these assumptions as best I can (under NT 4)}<br><br>&nbsp; &nbsp; try<br>&nbsp; &nbsp; &nbsp; while ReadFile(ReadHandle, ReadBuf, SizeOf(ReadBuf), BytesRead, nil) do<br>&nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; {There are much more efficient ways of doing this: we don't really<br>&nbsp; &nbsp; &nbsp; &nbsp; need two buffers, but we do need to scan for CR &amp; LF &amp;&amp;&amp;}<br>{$IFDEF Debug}<br>&nbsp; &nbsp; &nbsp; &nbsp; Inc(ReadCount);<br>{$ENDIF}<br>&nbsp; &nbsp; &nbsp; &nbsp; for &nbsp;i:= 0 to BytesRead - 1 do<br>&nbsp; &nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (ReadBuf = LF) then<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Newline:= true<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end else<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (ReadBuf = CR) then<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; OutputLine<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end else<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LineBuf[LineBufPtr]:= ReadBuf;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Inc(LineBufPtr);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if LineBufPtr &gt;= (SizeOf(LineBuf) - 1) then {line too long - force a break}<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; begin<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Newline:= true;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; OutputLine<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end<br>&nbsp; &nbsp; &nbsp; &nbsp; end<br>&nbsp; &nbsp; &nbsp; end;<br>&nbsp; &nbsp; &nbsp; WaitForSingleObject(ProcessInfo.hProcess, TerminationWaitTime);<br>&nbsp; &nbsp; &nbsp; {The child process may have closed its stdoutput handle but not yet<br>&nbsp; &nbsp; &nbsp; terminated, so will wait for up to five seconds to it a chance to<br>&nbsp; &nbsp; &nbsp; terminate. If it has not done so after this time, then we will end<br>&nbsp; &nbsp; &nbsp; up returning STILL_ACTIVE ($103)<br><br>&nbsp; &nbsp; &nbsp; If you don't care about the exit code of the process, then you don't<br>&nbsp; &nbsp; &nbsp; need this wait: having said that, unless the child process has a<br>&nbsp; &nbsp; &nbsp; particularly longwinded cleanup routine, the wait will be very short<br>&nbsp; &nbsp; &nbsp; in any event.<br>&nbsp; &nbsp; &nbsp; I recommend you leave this wait in unless you have an intimate<br>&nbsp; &nbsp; &nbsp; understanding of the child process you are spawining and are sure you<br>&nbsp; &nbsp; &nbsp; don't want to wait for it}<br><br>&nbsp; &nbsp; &nbsp; GetExitCodeProcess(ProcessInfo.hProcess, Result);<br>&nbsp; &nbsp; &nbsp; OutputLine {flush the line buffer}<br>&nbsp; &nbsp; finally<br>&nbsp; &nbsp; &nbsp; CloseHandle(ProcessInfo.hProcess)<br>&nbsp; &nbsp; end<br>&nbsp; finally<br>&nbsp; &nbsp; CloseHandle(ReadHandle);<br>&nbsp; end<br>end;<br><br>{$R *.DFM}<br><br>procedure TConsoleForm.RunButtonClick(Sender: TObject);<br>var<br>&nbsp; s: String;<br>&nbsp; CAExitCode: Integer;<br>begin<br>&nbsp; s:= CmdLineEdit.Text;<br>&nbsp; Output.Items.Clear;<br>&nbsp; Output.Items.Add('Executing ' + s);<br>&nbsp; CAExitCode:= ExecConsoleApp(s, Output.Items, RunningUpdate);<br>{$IFDEF DEBUG}<br>&nbsp; Output.Items.Add(Format('%s returned %d (rc = %d)', [s, CAExitCode, ReadCount]))<br>{$ELSE}<br>&nbsp; Output.Items.Add(Format('%s returned %d', [s, CAExitCode]))<br>{$ENDIF}<br>end;<br><br>procedure TConsoleForm.RunningUpdate(Sender: TObject);<br>begin<br>&nbsp; Output.Update &nbsp;{flush paint messages to show progress}<br>end;<br><br>procedure TConsoleForm.BrowseButtonClick(Sender: TObject);<br>begin<br>&nbsp; if BrowseDlg.Execute then<br>&nbsp; &nbsp; CmdLineEdit.Text:= BrowseDlg.Filename<br>end;<br><br>end.<br><br><br>
 
顶部