Recent

Author Topic: how to close or control an attached console from GUI  (Read 3002 times)

robert rozee

  • New Member
  • *
  • Posts: 43
how to close or control an attached console from GUI
« on: November 01, 2021, 03:59:54 pm »
hi,
    attached is an example of a GUI program that does almost exactly what i want, but not quite   :)   hopefully someone will be able to suggest how to get over the final hurdle.

three buttons on a form:

1. click on the first button, and a console is attached,
2. click on the second button, and a text message is written to the console via writeln(stderr, 'message'),
3. click on the third button, and the console is freed - the GUI application carries on working.

at the console:
4. press control-C, nothing happens. up until here, everything is going as planned.
5. click on the console's 'X' (close) button, and... without any intervention, the console and GUI both close. this is NOT what i am after. i'd be happy for either (a) just the console to close, or (b) nothing at all to happen.

for the moment, i've "thrown a spanner in the works" so to speak. a conveniently places showmessage() placed in the CTRL_CLOSE_EVENT handler code disrupts closing the GUI side of things, causing windows (XP) to pop up a "Windows cannot end this program" message after a few seconds, which i can then click 'Cancel' on and everything carries on happily. but this is rather messy.


any suggestions on how to 'fix' things so that the console either can not close itself at all, or is safely rendered incapable of closing the GUI?

Code: Pascal  [Select][+][-]
  1. function ConsoleHandler( dwCtrlType: DWORD ): BOOL; stdcall;
  2. begin
  3.   case dwCtrlType of CTRL_CLOSE_EVENT:begin            // console window closed
  4.                                         FreeConsole;
  5.                                         showmessage('here');
  6.                                         IsConsole:=false;
  7.                                         result:=true
  8.                                       end;
  9.                          CTRL_C_EVENT:result:=true     // Avoid terminating with Ctrl+C
  10.                   else                result:=false    // all others unhandled
  11.   end  { of case }
  12. end;
  13.  
  14.  
  15. procedure TForm1.Button1Click(Sender: TObject);
  16. begin
  17.   AllocConsole;      // in Windows unit
  18.   IsConsole:=True;   // in System unit
  19.   SysInitStdIO;      // in System unit
  20.   SetConsoleCtrlHandler({nil,} @ConsoleHandler, true)
  21. end;
  22.  
  23.  
  24. procedure TForm1.Button2Click(Sender: TObject);
  25. begin
  26.   writeln(stderr, 'hello world')
  27. end;
  28.  
  29.  
  30. procedure TForm1.Button3Click(Sender: TObject);
  31. begin
  32.   FreeConsole;
  33.   IsConsole:=false
  34. end;
« Last Edit: November 08, 2021, 04:36:21 am by robert rozee »

ASerge

  • Hero Member
  • *****
  • Posts: 1916
Re: how to close an attached console without closing GUI
« Reply #1 on: November 01, 2021, 06:58:08 pm »
From HandlerRoutine callback function:
Quote
The CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals give the process an opportunity to clean up before termination. A HandlerRoutine can perform any necessary cleanup, then take one of the following actions:
  • Call the ExitProcess function to terminate the process.
  • Return FALSE. If none of the registered handler functions returns TRUE, the default handler terminates the process.
  • Return TRUE. In this case, no other handler functions are called and the system terminates the process.
I.e. always "terminates the process".


robert rozee

  • New Member
  • *
  • Posts: 43
Re: how to close or control an attached console from GUI
« Reply #2 on: November 02, 2021, 01:36:07 pm »
managed to get things to a 'workable' stage, see attached zip file containing example project.

shifting the code that detaches the console out of the CTRL_CLOSE_EVENT handler fixed the need for a spanner-in-the-works to keep the GUI alive. the handler now just sets a flag (detach:=true), and this is picked up later by a timer event that carries out the FreeConsole, etc:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Timer1Timer(Sender: TObject);
  2. begin
  3.   if detach and IsConsole then
  4.   begin
  5.     detach:=false;
  6.     SetConsoleCtrlHandler(@ConsoleHandler, false);
  7.     IsConsole:=false;
  8.     FreeConsole                                        // takes approx 990ms
  9.   end
  10. end;        

the "Windows cannot end this program" query still pops up, but just hitting 'Cancel' closes this and the GUI carries on. tried hooking the console's message handler, but this proved evasive due to the console window not being owned by the GUI program.

found the following code to fix the StdIO setup, so that writeln('message') works - ie, no need to write to StdErr:

Code: Pascal  [Select][+][-]
  1.   if not IsConsole then
  2.   if AllocConsole<>BOOL(0) then                        // in Windows unit
  3.   begin
  4.     SetConsoleTitle('GFXterm - Diagnostics Output');
  5.     StdInputHandle:=0;
  6.     StdOutputHandle:=0;
  7.     StdErrorHandle:=0;
  8.     IsConsole:=True;                                   // in System unit
  9.     SysInitStdIO;                                      // in System unit
  10.     SetConsoleCtrlHandler(@ConsoleHandler, true)       // hook for ctrl-C, break, and close
  11.   end

the solution is now 'good enough' for my needs. hopefully the attached code may be useful to others!


cheers,
rob   :-)
« Last Edit: November 08, 2021, 04:45:29 am by robert rozee »

BobDog

  • Full Member
  • ***
  • Posts: 248
Re: how to close an attached console without closing GUI
« Reply #3 on: November 02, 2021, 07:26:05 pm »

Your compiled program executable is 23 Mbytes??
Here is a windows simulation:
Code: Pascal  [Select][+][-]
  1.  
  2. program consolestuff;
  3.  
  4. uses
  5. crt;
  6.  
  7. function msgbox(p:pointer;msg:ansistring;title:pchar;i1:int32):int32; external 'user32.dll' name 'MessageBoxA';
  8. function getconsole():boolean;external 'kernel32.dll' name 'AllocConsole';
  9. function hideconsole():boolean;external 'kernel32.dll' name 'FreeConsole';
  10. function gethandle(): int64;external 'kernel32.dll' name 'GetConsoleWindow';
  11. function GetSystemMenu(p:int64;b:boolean):pointer;external 'user32.dll' name 'GetSystemMenu';
  12. function EnableMenuItem(p:pointer;i1:integer;i2:integer):boolean;external 'user32.dll' name 'EnableMenuItem';
  13.  
  14. var
  15. msg:int32;
  16. h,counter:int64;
  17. flag:boolean=true;
  18. pt:pointer;
  19.  
  20. begin
  21. repeat
  22. msg:=msgbox(nil,'Yes = console'+#13#10+'No = no console'+#13#10+'Cancel = end the program','hello',3 );
  23. if (msg=2) then flag:=false;
  24. if (msg=7) then hideconsole;
  25. if (msg=6) then
  26. begin
  27. getconsole;
  28. counter:=counter+1;
  29. pt:=GetSystemMenu(gethandle(),false);
  30. EnableMenuItem(pt,61536,1);
  31. writeln('hello again ',counter,'  times');
  32. end;
  33. until flag=false;
  34. end.
  35.  

robert rozee

  • New Member
  • *
  • Posts: 43
Re: how to close or control an attached console from GUI
« Reply #4 on: November 07, 2021, 02:57:32 pm »
taking a slightly different tack, i've been experimenting with the GUI detecting if launched from a console and then simply taking over that console solely for itself. this was prompted by the discovery that even if the GUI's standard I/O was sent back to the console that launched it, the console still ALSO had a 'live' command processor attached to it   >:(   and so would respond to any keyboard input in a 'muddled' way.

the below is the results of experiments thus far, something that you just drop at the end of your main form:

Code: Pascal  [Select][+][-]
  1. var ProcessList:array [1..64] of DWORD;
  2.         AHandle:THandle;
  3.            I, N:integer;
  4.  
  5. begin
  6.   if AttachConsole(ATTACH_PARENT_PROCESS) then
  7.   begin
  8.     StdInputHandle:=0;
  9.     StdOutputHandle:=0;
  10.     StdErrorHandle:=0;
  11.     IsConsole:=True;                                   // in System unit
  12.     SysInitStdIO;                                      // in System unit
  13.     SetConsoleCtrlHandler(@ConsoleHandler, true);      // hook for ctrl-C, break, and close
  14.  
  15.     writeln;
  16.     writeln(StringOfChar('-', 72));
  17.     N:=GetConsoleProcessList(@ProcessList, 64);
  18.  
  19.     if N<=64 then
  20.     begin
  21.  
  22.       writeln('process', #9, 'handle', #9);
  23.  
  24.       for I:=1 to N do if ProcessList[I]=GetProcessID then writeln(ProcessList[I], #9#9, '(self)') else
  25.       begin
  26.         AHandle:=OpenProcess(PROCESS_TERMINATE, false, ProcessList[I]);        // NOT us
  27.         if (AHandle<>0) and TerminateProcess(AHandle,255) then writeln(ProcessList[I], #9, AHandle, #9, 'closed')
  28.                                                           else writeln(ProcessList[I], #9, AHandle, #9)
  29.  
  30.       end
  31.     end;
  32.  
  33.     writeln(StringOfChar('-', 72));
  34.     writeln
  35.   end
  36. end.
  37.  

what this does is:

1. try to attach back to the launching console. if this fails (as happens if there is no launching console), then carry on as a "GUI only" application.

2a. if a console was found, set up a control handler (ctrl-C, break, etc) for it,
2b. find all OTHER processes attached to that console and kill them,
2c. carry on as a "composite GUI + console" application. if either GUI or console is closed, then the other also closes.

this gives the GUI 100% exclusive access to the console, and appears to function (for my particular application) as a fairly clean solution.


but in an ideal world, it would be nice to be able to suspend the other process(es) attached to the console instead of killing them, and when the GUI is ready to exit simply resume them. but that has proven elusive. we can simply do a "START /WAIT project1", but the GUI has no way to tell if the user has actually started it this way or not and so is less than entirely bullet-proof.

any suggestions on how to suspend a console-attached process instead of killing it?


cheers,
rob   :-)
« Last Edit: November 08, 2021, 04:45:55 am by robert rozee »

ezlage

  • New Member
  • *
  • Posts: 29
    • GitHub
Re: how to close an attached console without closing GUI
« Reply #5 on: November 07, 2021, 04:31:14 pm »
Are you able to replace the closing mechanism with some other that just hides the window?

And if you build a custom launcher and, from it, you start, suspend, resume, finish or restart whatever you want through TProcess?

From the intermediate process you can send and receive data from the others via pipeline, as well as from SimpleIPC.

robert rozee

  • New Member
  • *
  • Posts: 43
Re: how to close or control an attached console from GUI
« Reply #6 on: November 07, 2021, 05:11:24 pm »
i had considered just that - having my executable launch a copy of itself, and one copy act as a hidden 'server' sitting between the console window and GUI copy. the problem with this, however, is a big chunk of memory resources (30mb) is thrown away supporting a seldom used (for my application) diagnostics feature. despite RAM being cheap and plentiful these days, it irks me! as for a custom launcher, i really want to keep to one, single, executable file.

my application, in its current form, is designed for Linux, and i am porting it to also run on Windows (win32/XP). the nice thing about Linux is that you can just talk to the console, and if it is not there nothing bad happens. also, when a GUI application is launched from a console the GUI application keeps control of the console (not detached, line under Windows). this is the kind of behaviour i'm after mimicking as far as is possible. Windows actually has a slight advantage, as it is able to detect the presence of a console, whereas Linux can not (and doesn't need to, as standard I/O without a console just disappears).

btw, i have found that i can suspend the 'command processor' process attached to the console, but when i go to resume it upon the GUI exiting, the console just sits frozen/dead.


cheers,
rob   :-)
« Last Edit: November 08, 2021, 04:46:26 am by robert rozee »

 

TinyPortal © 2005-2018