Console window only if run from a console window.

Windows specific forum
jassing
Addict
Addict
Posts: 1745
Joined: Wed Feb 17, 2010 12:00 am

Console window only if run from a console window.

Post by jassing »

I have a need to write to the console, only if the app is started in a console window; otherwise, just run silently.

If I compile the app as a console window but don't use openconsole(), when run from explorer, it opens a console window; which I don't want.

Is there a way to suppress opening a new console window & instead use an existing (parent) console window?

Looks like I might be able to use AttachConsole() (kernel32.dll); will try to see if I can beat that into working...
a quick go:

Code: Select all

Import "kernel32.lib"
  AttachConsole( processID )
EndImport
hCMDpid = 14648  ; Get PID for existing cmd.exe


If AttachConsole(hCMDpid) ; does not give focus to window
  OpenConsole()
  PrintN("Hi - press enter (twice) to finish exe")
  Input() ; Require user to hit 'enter' twice in cmd
  CloseConsole()
  FreeConsole_() ; Needed? or is this redundant to closeconsole()?
Else
  Debug "Failed to attach to console"
EndIf
jassing
Addict
Addict
Posts: 1745
Joined: Wed Feb 17, 2010 12:00 am

Re: Console window only if run from a console window.

Post by jassing »

It must be possible...
Some (useless) info here: https://blogs.msdn.microsoft.com/oldnew ... /?p=19643/
I like how he says you can't, but microsoft has done it with official tools..
I am not grasping the proper console API's for input; but the idea seems doable.
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Console window only if run from a console window.

Post by Shardik »

In your posted link Raymond Chen posted a link to a posting of Junfeng Zhang who described two solutions to start an app alternatively as a Console or GUI app. Did you already try out one of these?

I have taken my own try in PB by programming a console app and a GUI app. The console app "Console-Part.Exe" checks if there were any parameters given when launching "Console-Part.Exe" in the console. If there were none, it simply uses RunProgram("GUI-Part.Exe") to start the GUI app. Otherwise it attaches to the open console and displays the parameters. When starting from Windows Explorer you simply start "GUI-Part.Exe".

Console-Part.PB (please compile with "Executable format: Console"!):

Code: Select all

ParameterCount = CountProgramParameters()

If ParameterCount > 0
  If OpenConsole("Console-Part")
    For i = 1 To ParameterCount
      PrintN("Parameter " + Str(i) + ": " + ProgramParameter(i - 1))
    Next i
  EndIf
Else
  RunProgram("GUI-Part.Exe")
EndIf
GUI-Part.PB (please compile with "Executable format: Windows"!)

Code: Select all

MessageRequester("GUI-Part", "No console wanted!")
If you are in need to have the same name for both program parts you may use the trick Visual Studio utilizes: rename the extension of your program compiled with "Executable format: Console" from .Exe to .Com. When starting your program from Console simply don't add the extension (as a program with the .Com extension has priority in starting if the same program name with the .Exe extension is available). From Windows Explorer you should always start the program with the .Exe extension.
User avatar
Pierre Bellisle
User
User
Posts: 35
Joined: Wed Jun 27, 2018 5:12 am

Re: Console window only if run from a console window.

Post by Pierre Bellisle »

There is the console cursor position technique.
If it is at row 0, column 0 then the program was not started from a console prompt.

Code: Select all

#PB_Compiler_Processor        = #PB_Processor_x64 
#PB_Compiler_ExecutableFormat = #PB_Compiler_Console
;_____________________________________________________________________________

Procedure WinMain()

 Protected.CONSOLE_SCREEN_BUFFER_INFO ScreenInfo
 GetConsoleScreenBufferInfo_(GetStdHandle_(#STD_OUTPUT_HANDLE), ScreenInfo)
 If ScreenInfo\dwCursorPosition\x + ScreenInfo\dwCursorPosition\y = 0 ;IF it's a new console, cursor x and y will be zero
   FreeConsole_()
   MessageBox_(#HWND_DESKTOP, "Not started from the command prompt.", "Started from..." + Str(GetStdHandle_(#STD_OUTPUT_HANDLE)), #MB_OK | #MB_TOPMOST)
 Else
   Protected.s sBuff
   Protected.l Written
   sBuff = "Started from the command prompt."
   WriteConsole_(GetStdHandle_(#STD_OUTPUT_HANDLE), sBuff, Len(sBuff), Written, 0)
   MessageBox_(#HWND_DESKTOP, sBuff, "Started from..." + Str(GetStdHandle_(#STD_OUTPUT_HANDLE)), #MB_OK | #MB_TOPMOST)
EndIf

EndProcedure
;_____________________________________________________________________________

WinMain()
jassing
Addict
Addict
Posts: 1745
Joined: Wed Feb 17, 2010 12:00 am

Re: Console window only if run from a console window.

Post by jassing »

neat, cool trick.
Thanks.
But, one of the things I'm trying to avoid is the flashing of the console window that compiling as a console app creates.
That's one reason I was hoping for attachconsole() would be helpful.

Basically, I want to compile as a windows app, unless started from a console, then pump output to the console window.

It's easy enough by having two exe's, but that's not ideal.

-----------------------------------------------------------------

For anyone else that is curious -- this is the current WIP, I have not fully tested it, but it seems to work as I want/expect
(EXCEPT: input is all screwy. and "start /w" doesn't wait for input to be completed, see viewtopic.php?f=5&t=71624 for more info on the input issue)

(When compiling, target should be windows, not console)

Code: Select all

;- "new" auto-console (attach a console when run from cmd, otherwise, act like a windows app)
;- by jassing updated 2018-10-24

EnableExplicit

; -- Next 3 elements (structure,macro, procedure) are not my work, taken from https://www.purebasic.fr/english/viewtopic.php?f=3&t=51354 --
Structure CONSOLE_COORD
  StructureUnion
    coord.COORD
    long.l
  EndStructureUnion
EndStructure
Macro ConsoleHandle_() : GetStdHandle_( #STD_OUTPUT_HANDLE )  : EndMacro
Procedure ConsoleDeletePrevLines( CountLines = 1 )
   Protected ConsoleBufferInfo.CONSOLE_SCREEN_BUFFER_INFO
   Protected hConsole
   Protected location.CONSOLE_COORD
   
   If CountLines < 1 : ProcedureReturn #False : EndIf
   
   hConsole = ConsoleHandle_()
   GetConsoleScreenBufferInfo_( hConsole, @ConsoleBufferInfo )
   
   With location
     \coord\x = 0
     \coord\y = ConsoleBufferInfo\dwCursorPosition\y
     While CountLines And \coord\y
        \coord\y - 1
        SetConsoleCursorPosition_( hConsole, \long )
        Print( Space(ConsoleBufferInfo\dwSize\x) )
        If CountLines = 1
           SetConsoleCursorPosition_( hConsole, \long )
        EndIf
        CountLines - 1
     Wend
   EndWith 
   
   ProcedureReturn #True
 EndProcedure
 ; -- end not my work --
 
Import "kernel32.lib"
  AttachConsole( processID )
EndImport

Enumeration
  #ParentInfoPID
  #ParentInfoName
EndEnumeration

Procedure.s GetParentInfo(GetWhat, MyPid = 0 )
  Protected lppe.PROCESSENTRY32
  Protected hSnap = CreateToolhelp32Snapshot_(#TH32CS_SNAPPROCESS , 0)
  Static ParentPID, ParentName.s
  Protected.s ReturnInfo
  
  If ParentName = ""
    If MyPid = 0 
      MyPid = GetCurrentProcessId_()
    EndIf
    
    If hSnap <> #INVALID_HANDLE_VALUE
      lppe\dwSize = SizeOf(PROCESSENTRY32)
      If Process32First_(hSnap, @lppe)
          Repeat        
              If lppe\th32ProcessID = mypid 
                  ParentPID = lppe\th32ParentProcessID
                  Debug "my parent pid is = " + Str(ParentPID)
                  Break
              EndIf    
          Until Process32Next_(hSnap, @lppe) = 0
      EndIf        
  
      If Process32First_(hSnap, @lppe)
          Repeat        
            If lppe\th32ProcessID = ParentPID    
              ParentName = PeekS(@lppe\szExeFile[0])
                Debug "and its name is = " + ParentName
                Break
              EndIf    
          Until Process32Next_(hSnap, @lppe) = 0
      EndIf        
    EndIf
  EndIf
  
  Select GetWhat 
    Case #ParentInfoName
      ReturnInfo = ParentName
    Case #ParentInfoPID
      ReturnInfo = Str(ParentPID)
  EndSelect
  ProcedureReturn ReturnInfo
EndProcedure

Procedure _OpenConsole_()
  Protected hcmdpid = Val(GetParentInfo(#ParentInfoPID))  ; Get PID for existing cmd.exe
  Protected hConsoleID = AttachConsole(hCMDpid) 
  If hConsoleID
    ConsoleDeletePrevLines()
  EndIf 
  ProcedureReturn hConsoleID
EndProcedure
Macro IsConsole() : Bool(LCase(GetParentInfo(#ParentInfoName))="cmd.exe") : EndMacro

; for some reason, native PB console print functions don't work, but input does.
Procedure _Print_( PrintString.s)
  Protected bytesWritten
        
  WriteConsole_(ConsoleHandle_(), PrintString, Len(PrintString), @bytesWritten, 0)
  OutputDebugString_("Bytes written "+Str(bytesWritten))
EndProcedure

;- Macros to make use of some conventional console functions.
Macro PrintN( printString ) : _Print_( printString+#CRLF$ ) : EndMacro
Macro Print( printString) : _Print_(printstring) : EndMacro
Macro OpenConsole() : _OpenConsole_() : EndMacro
Macro CloseConsole() : FreeConsole_() : EndMacro

;- Example.
CompilerIf #PB_Compiler_IsMainFile
  
  If IsConsole() ; if parent is a command / console window
    If OpenConsole()
      PrintN("This is just some text...")
      PrintN("Hi - press 'enter' to finish exe")
      
      Input() 
      
      CloseConsole() 
    Else ; An error occured, shouldn't get here.
      Debug "Failed to attach to console"
      MessageRequester("Error","Failed to attach to a parent console",#PB_MessageRequester_Error)
    EndIf
  Else ; Program being run as a windows program
    OutputDebugString_("program run from "+GetParentInfo(#ParentInfoName))
    MessageRequester("Windows App","This was NOT run from a command prompt",#PB_MessageRequester_Info)
  EndIf

CompilerEndIf
Post Reply