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?
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>
Call AllocConsole to avoid the error 105.
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:
Yes you have to ship multiple executables. But doing so gives the user the best experience.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With