Console applications in Delphi
by Alex G. Fedorov, July 1997

e-mail: alexfedorov@oocities.com



In this article we will learn how to write console application in Delphi. Before we go into details, let's note than console applications is another kind of Windows application - the one which has all access to Win API, but has no GUI and runs in text mode.

Simple console program

There is no automatic way to create a console application in Delphi (maybe some tools writer will make expert for it one day) - you create an empty file and put the following lines into it:

  program ConPrg;
  {$APPTYPE CONSOLE}
  begin
  end.
 

Then you save this file with .dpr extention - conprg.dpr in this example. Next, you can load it into Delphi (File|Open) and start to add code.
NOTE: If you run the program above, it will end immediately, since there is nothing in it to do.

For the first step you can add readln line into it:

  program ConPrg;
  {$APPTYPE CONSOLE}
  begin
   readln
  end.
 

You 'll see an empty text-mode window, which will be closed when you press Enter key.

Going further

As we' ve pointed earlier, you can use nearly any Win32 API function from your console application. This is very handy since you just use Write/Writeln pair for output and don't think about user interface at all. Examples of Console application usage can be numerous: various kinds of utilities, sample programs to test one or other set of API functions and so on. We will not dig deeper into examples of how to use any particular API, just we will talk about Console API.

Console API

Microsoft provides a set of functions, which can be useful when you create console applications.First we should know that there is at least two handles, which is associated with console window. The one is for input and other is for output. Two short functions below shows how to get those handles.

//-----------------------------------------
// Get handle to console input
//-----------------------------------------
function GetConInputHandle : THandle;
begin
Result := GetStdHandle(STD_INPUT_HANDLE)
end;
//-----------------------------------------
// Get handle to console output
//-----------------------------------------
function GetConOutputHandle : THandle;
begin
Result := GetStdHandle(STD_OUTPUT_HANDLE)
end;

Next, since there is no simple functions for positioning cursor, clear the screen and show/hide cursor (as was in old days with CRT unit) we should to implement one. Here it is:

//-----------------------------------------
// Position cursor to X, Y
//-----------------------------------------
procedure GotoXY(X, Y : Word);
begin
Coord.X := X; Coord.Y := Y;
SetConsoleCursorPosition(ConHandle, Coord);
end;
//-----------------------------------------
// Clear Screen - fill it with spaces
//-----------------------------------------
procedure Cls;
begin
Coord.X := 0; Coord.Y := 0;
FillConsoleOutputCharacter(ConHandle, ' ', MaxX*MaxY, Coord, NOAW);
GotoXY(0, 0);
end;
//--------------------------------------
// Show/Hide cursor
//--------------------------------------
procedure ShowCursor(Show : Bool);
begin
CCI.bVisible := Show;
SetConsoleCursorInfo(ConHandle, CCI);
end;

As you can see, in previous examples we've used some functions from Console API: GetStdHandle, SetConsoleCursorPosition, FillConsoleOutputCharacter, SetConsoleCursorInfo. For example, we use two variables: MaxX and MaxY, which is of type WORD and holds the maximum horizontal and vertical size of the screen:

//--------------------------------------
// Initialize global variables
//--------------------------------------
procedure Init;
begin
// Get console output handle
ConHandle := GetConOutputHandle;
// Get max window size
Coord := GetLargestConsoleWindowSize(ConHandle);
MaxX := Coord.X;
MaxY := Coord.Y;
end;

We can even make a "message loop" - for those who start Windows programming with Delphi - this is a way to make Windows applications with plain API calls - you need at least WinMain, message loop and window proc. Try this some day and you'll love visual tools!

Here is the code for "message loop":

SetConsoleCtrlHandler(@ConProc, False);
Cls;
//
// "Message Loop"
//
Continue := True;
While Continue do
Begin
ReadConsoleInput(GetConInputHandle, IBuff, 1, IEvent);
Case IBuff.EventType of
KEY_EVENT : Begin
// Check for ESC Key and terminate program
If ((IBuff.KeyEvent.bKeyDown = True) AND
(IBuff.KeyEvent.wVirtualKeyCode = VK_ESCAPE)) Then
Continue := False;
End;
_MOUSE_EVENT : Begin
With IBuff.MouseEvent.dwMousePosition do
StatusLine(Format('%d, %d', [X, Y]));
End;
end;
End {While}

Also we can implelent an "event handler" and hook such events as Ctrl+C and Ctrl+Break key combos:

//-----------------------------------------------------
// Console Event Handler
//-----------------------------------------------------
function ConProc(CtrlType : DWord) : Bool; stdcall; far;
var
S : String;
begin
case CtrlType of
CTRL_C_EVENT : S := 'CTRL_C_EVENT';
CTRL_BREAK_EVENT : S := 'CTRL_BREAK_EVENT';
CTRL_CLOSE_EVENT : S := 'CTRL_CLOSE_EVENT';
CTRL_LOGOFF_EVENT : S := 'CTRL_LOGOFF_EVENT';
CTRL_SHUTDOWN_EVENT : S := 'CTRL_SHUTDOWN_EVENT';
else
S := 'UNKNOWN_EVENT';
end;
MessageBox(0, PChar(S + ' detected'), 'Win32 Console', MB_OK);
Result := True;
end;

To show everything in action I've created a short demo program, which uses routines mentioned below and some other techniques. Here is the full source of it. Enjoy!


{
[]-----------------------------------------------------------------------[]
      CON001 - Show various Console API functions. Checked with Win95

                             version 1.01

                  by Alex G. Fedorov, May-July, 1997
                      alexfedorov@oocities.com

   09-Jul-97   some minor corrections (shown in comments)
[]-----------------------------------------------------------------------[]
}
program Con001;
{$APPTYPE CONSOLE}
uses Windows, SysUtils;

const
// Some common colors
 YellowOnBlue = FOREGROUND_GREEN OR FOREGROUND_RED OR
                FOREGROUND_INTENSITY OR BACKGROUND_BLUE;
 WhiteOnBlue  = FOREGROUND_BLUE OR FOREGROUND_GREEN OR
                FOREGROUND_RED OR FOREGROUND_INTENSITY OR
                BACKGROUND_BLUE;
 RedOnWhite   = FOREGROUND_RED OR FOREGROUND_INTENSITY OR
                BACKGROUND_RED OR BACKGROUND_GREEN OR BACKGROUND_BLUE
                OR BACKGROUND_INTENSITY;
 WhiteOnRed   = BACKGROUND_RED OR BACKGROUND_INTENSITY OR
                FOREGROUND_RED OR FOREGROUND_GREEN OR FOREGROUND_BLUE
                OR FOREGROUND_INTENSITY;

var
 ConHandle  : THandle; // Handle to console window
 Coord      : TCoord;  // To store/set screen position
 MaxX, MaxY : Word;    // To store max window size
 CCI        : TConsoleCursorInfo;
 NOAW       : LongInt; // To store results of some functions

//-----------------------------------------
//      Get handle to console input
//-----------------------------------------
function GetConInputHandle : THandle;
begin
 Result := GetStdHandle(STD_INPUT_HANDLE)
end;
//-----------------------------------------
//      Get handle to console output
//-----------------------------------------
function GetConOutputHandle : THandle;
begin
 Result := GetStdHandle(STD_OUTPUT_HANDLE)
end;
//-----------------------------------------
//        Position cursor to X, Y
//-----------------------------------------
procedure GotoXY(X, Y : Word);
begin
 Coord.X := X; Coord.Y := Y;
 SetConsoleCursorPosition(ConHandle, Coord);
end;
//-----------------------------------------
//   Clear Screen - fill it with spaces
//-----------------------------------------
procedure Cls;
begin
 Coord.X := 0; Coord.Y := 0;
 FillConsoleOutputCharacter(ConHandle, ' ', MaxX*MaxY,  Coord, NOAW);
 GotoXY(0, 0);
end;
//--------------------------------------
//           Show/Hide cursor
//--------------------------------------
procedure ShowCursor(Show : Bool);
begin
 CCI.bVisible := Show;
 SetConsoleCursorInfo(ConHandle, CCI);
end;
//--------------------------------------
//     Initialize global variables
//--------------------------------------
procedure Init;
begin
// Get console output handle
 ConHandle := GetConOutputHandle;
// Get max window size
 Coord := GetLargestConsoleWindowSize(ConHandle);
 MaxX := Coord.X;
 MaxY := Coord.Y;
end;
//---------------------------------------
//          Draw "status line"
//---------------------------------------
procedure StatusLine(S : String);
begin
 Coord.X := 0; Coord.Y := 0;
 WriteConsoleOutputCharacter(ConHandle, PChar(S),   Length(S)+1, Coord, NOAW);
 FillConsoleOutputAttribute (ConHandle, WhiteOnRed, Length(S),   Coord, NOAW);
end;

//-----------------------------------------------------
//               Console Event Handler
//-----------------------------------------------------
function ConProc(CtrlType : DWord) : Bool; stdcall; far;
var
 S : String;
begin
 case CtrlType of
   CTRL_C_EVENT        : S := 'CTRL_C_EVENT';
   CTRL_BREAK_EVENT    : S := 'CTRL_BREAK_EVENT';
   CTRL_CLOSE_EVENT    : S := 'CTRL_CLOSE_EVENT';
   CTRL_LOGOFF_EVENT   : S := 'CTRL_LOGOFF_EVENT';
   CTRL_SHUTDOWN_EVENT : S := 'CTRL_SHUTDOWN_EVENT';
  else
   S := 'UNKNOWN_EVENT';
 end;
 MessageBox(0, PChar(S + ' detected'), 'Win32 Console', MB_OK);
 Result := True;
end;
{
[]---------------------------------------------------------------[]
  Main program - shows usage of some subroutines above and some
  of console API functions
[]---------------------------------------------------------------[]
}
var
 R        : TSmallRect;
 Color    : Word;
 OSVer    : TOSVersionInfo;
 IBuff    : TInputRecord;
 IEvent   : DWord;
 Continue : Bool;

begin
// Initialize global variables
 Init;
// Position window on screen
 With R do
{!! 1.01 !!}
  begin
   Left  := 10;  Top    := 10; Right := 40;  Bottom := 40;
  end
{!! 1.01 !!}
 SetConsoleWindowInfo(ConHandle, False, R);
// Set event handler
 SetConsoleCtrlHandler(@ConProc, True);
// Check event handler
 GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
// Change window title
 SetConsoleTitle('Console Demo');
// Hide cursor
 ShowCursor(False);
 Coord.X := 0; Coord.Y := 0;
// Set white text on blue
 Color := WhiteOnBlue;
 FillConsoleOutputAttribute(ConHandle, Color, MaxX*MaxY, Coord, NOAW);
// Console Code Page API is not supported under Win95 - only GetConsoleCP
 Writeln('Console Code Page = ', GetConsoleCP);
 Writeln('Max X=', MaxX,' Max Y=', MaxY);
 Readln;            // wait for user input
 Cls;               // clear screen
 ShowCursor(True);  // show cursor
//
// Use some Win32API stuff
//
 OSVer.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
 GetVersionEx(OSVer);
 With OSVer do
  Begin
   Writeln('dwMajorVersion = ', dwMajorVersion);
   Writeln('dwMinorVersion = ', dwMinorVersion);
   Writeln('dwBuildNumber  = ', dwBuildNumber);
   Writeln('dwPlatformID   = ', dwPlatformID);
  End;

//
 Readln;           // wait for user input
// Remove event handler
 SetConsoleCtrlHandler(@ConProc, False);
 Cls;
//
// "Message Loop"
//
 Continue := True;
 While Continue do
 Begin
  ReadConsoleInput(GetConInputHandle, IBuff, 1, IEvent);
  Case IBuff.EventType of
   KEY_EVENT   : Begin
// Check for ESC Key and terminate program
                   If ((IBuff.KeyEvent.bKeyDown = True) AND
                       (IBuff.KeyEvent.wVirtualKeyCode = VK_ESCAPE)) Then
                       Continue := False;
                  End;
   _MOUSE_EVENT : Begin
                   With IBuff.MouseEvent.dwMousePosition do
                    StatusLine(Format('%d, %d', [X, Y]));
                  End;
  end;
 End {While}
end.


Download source