How to write StdOut from a GUI application launched from the command line?

I am writing a standard Windows application in Delphi 7.

If I were writing a console application, I can call the following to output to a cmd line or an output file.

writeln('Some info'); 

If I do this from my standard GUI, which I started from the command line, I get an error.

 I/O Error 105 

There should be a simple solution to this problem. Basically, I want my application to have two modes: a graphical interface and a mode without a GUI. How to configure it correctly so that I can return to cmd window?

+5
source share
8 answers

This question is very similar (if not quite the same) as what I was trying to accomplish. I wanted to determine if my application was run from cmd.exe and send the output to the parent console, otherwise gui will appear in it. The answers here helped me solve my problem. 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 launch my graphical application from the command line and output the output to the same console on which my application was running. Otherwise, it will launch the full part of the GUI application.

Example output from the 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> 
+10
source

Call AllocConsole to avoid error 105.

+6
source

There is no reliable way to connect a GUI application to the console of its parent process. If you try to do this, you will get two active processes sharing the same console. This does not lead to trouble.

An alternative, while saving only one executable, as suggested by bummi, is that the console application frees the console if it is asked to run in GUI mode. This is a better approach, but causes the console window to blink and then close when you want to work in GUI mode.

The best discussion of the topic that I came across in Stack Overflow is Rob Kennedy's excellent answer: Can a single executable be a console and graphical application?

I believe that, as you say in the comments, 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 used:

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

Yes, you have to send several executables. But it gives the user a better experience.

+5
source

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
source

FWIW, I played with this problem and came after AttachConsole , which seems to do the trick. The only problem I encountered with my code is that the program will not give the console without an extra ENTER key or two. This is not real polish since I tried to fix this problem and (kind of) gave up. Perhaps someone here will see this?

 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. 
+1
source

AttachConsole seems to work as above, it is waiting for ENTER.

However, the program is still the winner program, not the console program, because dos sees it, and so cmd moves on to the next command after it starts.

 test.exe & dir 

shows the directory first, then test.exe output

 start /w test.exe & dir 

works and does not pause for the ENTER key

BTW, offer above: PostMessage (GetCurrentProcess, $ 0101, $ 0D, 0); does ENTER but gives a bong noise.

0
source

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

I made a block to make AttachConsole, connect an exception handler to display messages on the console.

To use it, you only need to call ATTACH in your code. It is best to enable command-line option bindings, such as -console

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

This is for a GUI application, and when using this, you should use START / W to start your program, you expect it to be blocked on the command line / batch, for example. start /w myprogram.exe -console

One of the advantages is that you can run it offline using the console if you want, and view 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 eg -console // if FindCmdLineSwitch('console',true) then AttachConsole(true,true); // When using this, you will use START to launch your program eg // 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) //ie 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. 
0
source

I also summarized this topic in a report with a running script:

http://www.softwareschule.ch/download/maxbox_starter70.pdf as a second backup:

https://www.slideshare.net/maxkleiner1/nogui-maxbox-starter70

The main routine has a native entry to separate from the entry:

  for it:=1 to 50 do if IsPrime(it) then NativeWriteln(IntToStr(it)+' is prime'); 
-one
source

Source: https://habr.com/ru/post/1496290/


All Articles