9

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?

Toby Allen
  • 10,997
  • 11
  • 73
  • 124
  • What you are asking has been asked a million times before on the stack and the answer is always that it cannot be done in a clean way. There's no way to determine whether or not there's a console that you can attach to. And no reason for that console to stay alive. Bottom line, if you want a console, be a console app. – David Heffernan Aug 10 '13 at 15:27
  • so is the solution to have two apps then? A console app and a standard forms app? – Toby Allen Aug 11 '13 at 08:31
  • Well, I don't know what your problem is. So I'd be loathe to propose a solution. Perhaps you want your own private console. Use AllocConsole for that. – David Heffernan Aug 11 '13 at 08:34
  • All I want to be able to do is call my app from the command line have it carry out a task (copy some file based on passed in parameters) output some text ('3 Files copied') and exit, but I appear not to be able to write out to the commandline that started the app. Ideally the same EXE would also be a GUI app that I could change settings. If this isnt possible, I'll need to do two apps. – Toby Allen Aug 11 '13 at 08:36

8 Answers8

13

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>
Tad Adams
  • 175
  • 2
  • 10
  • This is OK if you are just looking to display output into the command line. But operations like redirecting output into a file for example are not working. `start /wait ParentChecker.exe > out.txt` would still output into console and not into file out.txt – drinovc Feb 02 '17 at 21:28
  • If you are this deep into the source code then you could easily add parameters to your program to write output to a file instead of the console. i.e. -o out.txt, Since it's your program doing the writing, you can write wherever you want. – Tad Adams Oct 09 '17 at 12:00
  • @TadAdams Unless the code is DUnit, and you want to capture the `WriteLn` text output of the `TTextTestListener`. – Ian Boyd Mar 22 '22 at 21:10
6

Call AllocConsole to avoid the error 105.

Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • 1
    The intention is to connect to the parent process console, not make a new one. Now, a GUI subsystem app cannot reliably do what Toby wants, but AllocConsole is surely not what is desired also. – David Heffernan Aug 12 '13 at 06:10
6

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.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Creating two separate executables is probably best. Referring to above (which was just trying to see what could be done anyway), if I were to actually do something, I'd probably use that as a detect on a front-end (writing/reading nothing itself) and then call whichever exec is proper. – Glenn1234 Aug 11 '13 at 21:33
  • @glenn that would not work either. You'd end up with two active processes connected to the console. – David Heffernan Aug 12 '13 at 06:07
4

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.
bummi
  • 27,123
  • 14
  • 62
  • 101
  • @DavidHeffernan maybe I overlooked something, but I can't find more than one process. – bummi Aug 12 '13 at 06:25
  • coming from `Basically I want my app to have two modes, a GUI mode and a non-GUI mode.` I am not attaching a console but run as `{$APPTYPE CONSOLE}` if `IsConsoleMode`, in the other case I am freeing the console to run in GUI Mode. – bummi Aug 12 '13 at 06:47
  • OK, this is the other way around. In this way you get a console window shown when you start from, say, a shortcut. What you are trying to do cannot be done. Note how other apps handle this issue. For example, python.exe and pythonw.exe. – David Heffernan Aug 12 '13 at 08:05
1

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.
Glenn1234
  • 2,542
  • 1
  • 16
  • 21
  • My best guess is that it's going right back to the prompt upon first run, and this is why the extra keypresses? – Glenn1234 Aug 10 '13 at 08:13
  • From the code in your answer it looks like you started a console program and stripped out the "apptype console" directive, but didn't add the application initialize/run stuff to start a gui app. That would mean the program indeed runs without the standard console, finished immediately as it does not enter the main gui loop, and the attached "extra" console is sort of left in limbo? – Marjan Venema Aug 10 '13 at 10:14
  • @marjan what is attempted here doesn't work. You cannot expect anything good when >1 process attaches to the same console. – David Heffernan Aug 11 '13 at 14:57
  • @DavidHeffernan: a GUI process could (safely) create/attach to its own console, but indeed attempting to use your parent's console will lead to ... hmmmmpf ... misery. Missed that Glenn was doing that. – Marjan Venema Aug 11 '13 at 16:15
  • You could use `PostMessage(GetCurrentProcess,WM_KEYUP,VK_RETURN,0);` to send that extra ENTER key hit – vhanla Jul 24 '16 at 18:26
0

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.

Henry Crun
  • 235
  • 1
  • 11
  • >but indeed attempting to use your parent's console will lead to ... hmmmmpf ... misery. – Henry Crun Nov 16 '16 at 20:23
  • @DavidHeffernan: Can you say why it results in misery? When I consider the Linux case, where multiple tasks are put into the background, it would seem that they all remain attached to the console, and can send messages to it. Obviously confusion can result.... – Henry Crun Nov 16 '16 at 20:26
0

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.
Henry Crun
  • 235
  • 1
  • 11
-1

I did summarize this topic in a report with a running script too:

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 nativewriteline to separate from writeline:

 for it:=1 to 50 do if IsPrime(it) then NativeWriteln(IntToStr(it)+' is prime');
Max Kleiner
  • 1,442
  • 1
  • 13
  • 14