How do I write to StdOut from a GUI app started from the command line?

匿名 (未验证) 提交于 2019-12-03 03:03:02

问题:

I am writing a standard windows app in Delphi 7.

If I was writing a console app, I can call the following to output to the cmd line or output file.

writeln('Some info'); 

If I do this from my standard GUI app that I have started from the command line I get an error.

I/O Error 105 

There must be a simple solution to this problem. Basically I want my app to have two modes, a GUI mode and a non-GUI mode. How do I set it up correctly so I can write back to the cmd window?

回答1:

This question is very similar (if not exactly the same) as something I was trying to accomplish. I wanted to detect if my app was executed from a cmd.exe and send output to the parent console, otherwise it would display a gui. The answers here helped me solve my issue. Here is the code I came up with as an experiment:

ParentChecker.dpr

program ParentChecker;  uses   Vcl.Forms,   SysUtils,   PsAPI,   Windows,   TLHelp32,   Main in 'Main.pas' {frmParentChecker};  {$R *.res}  function AttachConsole(dwProcessID: Integer): Boolean; stdcall; external 'kernel32.dll'; function FreeConsole(): Boolean; stdcall; external 'kernel32.dll';  function GetParentProcessName(): String; const   BufferSize = 4096; var   HandleSnapShot: THandle;   EntryParentProc: TProcessEntry32;   CurrentProcessId: THandle;   HandleParentProc: THandle;   ParentProcessId: THandle;   ParentProcessFound: Boolean;   ParentProcPath: String; begin   ParentProcessFound:=False;   HandleSnapShot:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);   if HandleSnapShot<>INVALID_HANDLE_VALUE then   begin     EntryParentProc.dwSize:=SizeOf(EntryParentProc);     if Process32First(HandleSnapShot,EntryParentProc) then     begin       CurrentProcessId:=GetCurrentProcessId();       repeat         if EntryParentProc.th32ProcessID=CurrentProcessId then         begin           ParentProcessId:=EntryParentProc.th32ParentProcessID;           HandleParentProc:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,False,ParentProcessId);           if HandleParentProc<>0 then           begin             ParentProcessFound:=True;             SetLength(ParentProcPath,BufferSize);             GetModuleFileNameEx(HandleParentProc,0,PChar(ParentProcPath),BufferSize);             ParentProcPath:=PChar(ParentProcPath);             CloseHandle(HandleParentProc);           end;           Break;         end;       until not Process32Next(HandleSnapShot,EntryParentProc);     end;     CloseHandle(HandleSnapShot);   end;   if ParentProcessFound then Result:=ParentProcPath   else Result:=''; end;  function IsPrime(n: Integer): Boolean; var   i: Integer; begin   Result:=False;   if n<2 then Exit;   Result:=True;   if n=2 then Exit;   i:=2;   while i<(n div i + 1) do   begin     if (n mod i)=0 then     begin       Result:=False;       Exit;     end;     Inc(i);   end; end;  var   i: Integer;   ParentName: String;  begin   ParentName:=GetParentProcessName().ToLower;   Delete(ParentName,1,ParentName.LastIndexOf('\')+1);   if ParentName='cmd.exe' then   begin     AttachConsole(-1);     Writeln('');     for i:=1 to 100 do if IsPrime(i) then Writeln(IntToStr(i)+' is prime');     FreeConsole();   end   else   begin     Application.Initialize;     Application.MainFormOnTaskbar:=True;     Application.CreateForm(TfrmParentChecker, frmParentChecker);     frmParentChecker.Label1.Caption:='Executed from '+ParentName;     Application.Run;   end; end. 

Main.pas (form with label):

unit Main;  interface  uses   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,   Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, RzLabel;  type   TfrmParentChecker = class(TForm)     Label1: TLabel;   private     { Private declarations }   public     { Public declarations }   end;  var   frmParentChecker: TfrmParentChecker;  implementation  {$R *.dfm}  end. 

This allows me to run my GUI app from a command prompt and display output to the same console where my app was launched. Otherwise, it will run the full GUI part of the app.

Example output from console window:

I:\Delphi\Tests and Demos\ParentChecker\Win32\Debug>start /wait ParentChecker.exe  2 is prime 3 is prime 5 is prime 7 is prime 11 is prime 13 is prime 17 is prime 19 is prime 23 is prime 29 is prime 31 is prime 37 is prime 41 is prime 43 is prime 47 is prime 53 is prime 59 is prime 61 is prime 67 is prime 71 is prime 73 is prime 79 is prime 83 is prime 89 is prime 97 is prime  I:\Delphi\Tests and Demos\ParentChecker\Win32\Debug> 


回答2:

Call AllocConsole to avoid the error 105.



回答3:

I'm not quite sure what you are trying to achieve.
As I understood the question one way could be

program Project1; {$APPTYPE CONSOLE}  uses   Forms, Classes, Windows,   Unit1 in 'Unit1.pas' { Form1 } ; {$R *.res}  var   Finished: Boolean;   Input: String;  function IsConsoleMode(): Boolean; var   SI: TStartupInfo; begin   SI.cb := SizeOf(TStartupInfo);   GetStartupInfo(SI);   Result := ((SI.dwFlags and STARTF_USESHOWWINDOW) = 0); end;  procedure HandleInput; begin   Finished := Input = 'quit';   if not Finished then   begin     Writeln('Echo: ' + Input);   end   else     Writeln('Bye'); end;  begin   if IsConsoleMode then   begin     Finished := false;     Writeln('Welcome to console mode');     while not Finished do     begin       readln(Input);       HandleInput;     end;   end   else   begin     Writeln('Entering GUI Mode');     FreeConsole;     Application.Initialize;     Application.MainFormOnTaskbar := True;     Application.CreateForm(TForm1, Form1);     Application.Run;   end;  end. 


回答4:

There's no reliable way for a GUI subsystem application to attach to the console of its parent process. If you try to do so you end up with two active processes sharing the same console. This leads to no end of trouble.

The alternative, whilst retaining just a single executable, as suggested by bummi, is to have a console app that frees its console if it is asked to run in GUI mode. This is a better approach, but leads to a console window flashing up, and then closing, when you want to run in GUI mode.

The best discussion of the subject that I have come across on Stack Overflow is Rob Kennedy's superb answer: Can one executable be both a console and GUI application?

I believe, from what you say in comments, that the best option for you is to create two separate executables. One for the GUI subsystem, and one for the console subsystem. This is the approach taken by:

  • Java: java.exe, javaw.exe.
  • Python: python.exe, pythonw.exe.
  • Visual Studio: devenv.com, devenv.exe.

Yes you have to ship multiple executables. But doing so gives the user the best experience.



回答5:

FWIW, I played around with this problem and happened upon AttachConsole which seems to do the trick. The only problem I ran into with my code is that the program won't give the console up without an extra ENTER key or two. It's not real polished since I was trying to fix that problem and (kind of) gave up. Perhaps someone here will see it?

program writecon; uses windows, dialogs;    function AttachConsole(dwProcessID: DWord): BOOL; stdcall; external 'kernel32.dll';    function load_attach_console: boolean;     begin       Result := AttachConsole(-1);     end;    begin     // the function requires XP or greater, you might want to check for that here.     if load_attach_console = true then       begin         writeln;         writeln('This is running in the console.');         write('Press ENTER to continue.');         readln;         // from the linked page, you have to detach yourself from the console         // when you're done, this is probably where the problem is.         Flush(Output);         Flush(Input);         FreeConsole;       end     else       MessageDlg('This is not running in the console.', mtInformation, [mbOk], 0);   end. 


回答6:

AttachConsole does seem to work, as noted above it waits for ENTER.

However, the program is still a win prog and not a console program as far as dos sees it, and so cmd proceeds to the next command after launching it.

test.exe & dir 

shows the dir listing first, then the output from test.exe

start /w test.exe & dir  

does work, and does not pause for ENTER key

BTW, the suggestion above: PostMessage(GetCurrentProcess,$0101,$0D,0); does the ENTER but is giving a bong noise.



回答7:

I found this very complete article about the whole issue: http://www.boku.ru/2016/02/28/posting-to-console-from-gui-app/

I made a unit to do the AttachConsole, hook the exception handler to mirror messages to console.

To use it, you only need to call ATTACH in your code. It is best to make attaching a commandline option e.g -console

if FindCmdLineSwitch('console',true) then AttachConsole(true,true); 

This is for a gui application, and when using this, you must use START /W to launch your program is you expect it to be blocking on the commandline/batch e.g. start /w myprogram.exe -console

One handy benefit is that you can launch it standalone with a console if you want, and get to see all the error messages in the console.

unit ConsoleConnector; // Connects the/a console to a GUI program // Can hook exception handler to mirror messages to console. // To use it, you only need to call ATTACH // best to make attaching a commandline option e.g -console //    if FindCmdLineSwitch('console',true) then AttachConsole(true,true); // When using this, you will use START to launch your program e.g. // start /w myprogram.exe -console // creates Console var at end in initialise/finalise - you might want to do this explicitly in your own program instead. // see: http://www.boku.ru/2016/02/28/posting-to-console-from-gui-app/  //sjb 18Nov16  interface uses sysutils,forms;  type   TConsoleConnector = class   private     OldExceptionEvent:TExceptionEvent;     Hooked:boolean;     BlockApplicationExceptionHandler:boolean; //errors ONLY to console, no error messageboxes blocking program     procedure DetachErrorHandler;     procedure GlobalExceptionHandler(Sender: TObject; E: Exception);     procedure HookExceptionHandler;   public     IsAttached:boolean;      function Attach(         CreateIfNeeded:boolean=true; //Call ALLOCCONSOLE if no console to attach to         HookExceptions:boolean=false;  //Hook Application.OnException to echo all unhandled exceptions to console         OnlyToConsole:boolean=false  // Suppresses exception popups in gui, errors only go to console         ):boolean;     procedure Detach;            //detach and unhook     procedure writeln(S:string); //only writes if console is attached     procedure ShowMessage(S:string); //Popup ShowMessage box and mirror to console. Obeys OnlyToConsole   end;    var Console:TConsoleConnector;  implementation  uses Windows,dialogs;  //winapi function function AttachConsole(dwProcessId: Int32): boolean; stdcall; external kernel32 name 'AttachConsole';  function TConsoleConnector.Attach(CreateIfNeeded:boolean=true;HookExceptions:boolean=false;OnlyToConsole:boolean=false):boolean; begin   IsAttached:=AttachConsole(-1);   if not IsAttached and CreateIfNeeded     then begin       IsAttached:=AllocConsole;     end;   result:=IsAttached;   if HookExceptions then HookExceptionHandler; end;  procedure TConsoleConnector.Detach; begin   FreeConsole;   IsAttached:=false;   DetachErrorHandler; end;  procedure TConsoleConnector.WriteLn(S:string); begin   if IsAttached then system.writeln(S); end; procedure TConsoleConnector.ShowMessage(S:string); begin   self.Writeln(S);   if BlockApplicationExceptionHandler then exit;   dialogs.ShowMessage(S); end; procedure TConsoleConnector.GlobalExceptionHandler(Sender: TObject; E: Exception); begin   self.Writeln(E.Message);   if BlockApplicationExceptionHandler then exit;   if assigned(OldExceptionEvent) //i.e there was an old event before we hooked it     then OldExceptionEvent(Sender,E)     else Application.ShowException(E); end;  procedure TConsoleConnector.HookExceptionHandler; begin   OldExceptionEvent:=Application.OnException;   Application.OnException:=GlobalExceptionHandler;   Hooked:=true; end;  procedure TConsoleConnector.DetachErrorHandler; begin   if Hooked //I have hooked it     then begin       Application.OnException:=OldExceptionEvent;       OldExceptionEvent:=nil;       Hooked:=false;     end; end;  initialization   Console:=TconsoleConnector.create; finalization   Console.Detach;   Console.Destroy; end. 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!