How do I handle Ctrl+C in a Delphi console application?

Go To StackoverFlow.com

9

Are there best practices and code snippets available which show how I can handle Ctrl+C in a Delphi console application?

I have found some articles which give some information about possible problems with the debugger, with exception handling, unloading of DLLs, closing of stdin, and finalization for example this CodeGear forums thread.

2009-06-16 08:39
by mjn


17

From Windows API (MSDN):

BOOL WINAPI SetConsoleCtrlHandler(
    PHANDLER_ROUTINE HandlerRoutine,    // address of handler function  
    BOOL Add    // handler to add or remove 
   );   

A HandlerRoutine function is a function that a console process specifies to handle control signals received by the process. The function can have any name.

BOOL WINAPI HandlerRoutine(
    DWORD dwCtrlType    //  control signal type
   );   

In the Delphi the handler routine should be like:

function console_handler( dwCtrlType: DWORD ): BOOL; stdcall;
begin
  // Avoid terminating with Ctrl+C
  if (  CTRL_C_EVENT = dwCtrlType  ) then
    result := TRUE
  else
    result := FALSE;
end;
2009-06-16 09:13
by Nick Dandoulakis
Ok, this shows how I can disable Ctrl+C - I should clarify my question: how do I perform a clean application shutdown after detecting Ctrl+C - mjn 2009-06-16 09:26
Oh, I see. If you trap the Ctrl+C, like my example, you can set a sort of 'flag' and terminate normally whenever you want - Nick Dandoulakis 2009-06-16 09:41
I just remembered the "break := false;" that I used to put in my Turbo Pascal programs to disable Ctrl-Break. Ah, nostalgia.. - Nick Dandoulakis 2009-06-16 12:19
On very important thing to remember is that HandlerRoutine runs in a SEPARATE THREAD. If you don't start any other threads yourself, set the IsMultithread global variable to True so that any memory operations you perform will be safe - Rob Kennedy 2009-06-16 13:21
Also important is the calling convention. The callback function must use the stdcall calling convention or else your program will crash mysteriously - Rob Kennedy 2009-06-16 13:25
Thanks Rob, I forgot it. From now on it's a wiki, to further improve it - Nick Dandoulakis 2009-06-16 13:48
To install this handler : Windows.setConsoleCtrlHandler ( @console_handler, True {add } ) - STB Land 2012-03-30 11:07
hi @STBLand, please feel free to edit the answer - Nick Dandoulakis 2012-03-30 13:36


2

I wrote a little program to show you how to stop properly a background task. Hopefully it is clearer.

Content of ThreadConsoleApplication.dpr file :

program ThreadConsoleApplication;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Windows,
  Classes;

type
  { **
    * Classe TQueueReaderTestApplication
    * }
  TThreadConsoleApplication = class(TThread)
  public
    procedure Execute; override;

    constructor Create; virtual;
    destructor Destroy; override;

    class function getInstance: TThreadConsoleApplication;
  end;

function TThreadConsoleApplication_consoleCtrlHandler(dwCtrlType: DWORD): BOOL;
  stdcall; forward;

{ **
  * Classe TQueueReaderTestApplication
  * }

constructor TThreadConsoleApplication.Create;
begin
  inherited Create(True { CreateSuspended } );
  Windows.setConsoleCtrlHandler(@TThreadConsoleApplication_consoleCtrlHandler,
    True { add } );
end;

var
  threadConsoleApplicationInstance: TThreadConsoleApplication = nil;

destructor TThreadConsoleApplication.Destroy;
begin
  threadConsoleApplicationInstance := nil;
  inherited;
end;

procedure TThreadConsoleApplication.Execute;
begin
  System.Writeln('[TThreadConsoleApplication.Execute] begin');
  try
    while not Terminated do
    begin
      System.Writeln('[TThreadConsoleApplication.Execute] running ...');
      Windows.Sleep(1000 { dwMilliseconds } );
    end;
  finally
    System.Writeln('[TThreadConsoleApplication.Execute] end');
  end;
end;

class function TThreadConsoleApplication.getInstance: TThreadConsoleApplication;
begin
  if nil = threadConsoleApplicationInstance then
  begin
    threadConsoleApplicationInstance := TThreadConsoleApplication.Create;
  end;
  Result := threadConsoleApplicationInstance;
end;

function TThreadConsoleApplication_consoleCtrlHandler(dwCtrlType: DWORD): BOOL;
begin
  Result := False;
  if Windows.CTRL_C_EVENT = dwCtrlType then
  begin
    TThreadConsoleApplication.getInstance.Terminate;
    Result := True;
  end;
end;

var
  thread: TThread;

begin
  System.Writeln('[program] begin');
  try
    thread := nil;
    try
      thread := TThreadConsoleApplication.getInstance;
      thread.Resume;
      System.Writeln('[program] press a CTRL+C to stop running');
      thread.WaitFor;
    finally
      thread.Free;
    end;
    System.Writeln('[program] end');
  except
    on E: Exception do
    begin
      System.Writeln(System.ErrOutput, '[program] end with error');
      System.Writeln(System.ErrOutput, E.ClassName, ': ', E.Message);
    end;
  end;
  System.Writeln('[program] press a key to quit');
  System.Readln;

end.
2012-04-18 08:36
by STB Land