Proper Catastrophic Error Handling - delphi

There's something I keep running into that I really haven't solved with Delphi programs and was wondering if anyone could instruct me on it. As the topic says, how do you do proper catastrophic error handling? For instance:
// is file necessary for the program present?
if not FileExists(FilePath1) then
begin
raise Exception.Create(FilePath1 + ' does not exist and is required for this program to function.');
// I obviously need to do something here to make the program QUIT and not have
// any more code run.
Application.Terminate;
Abort;
end;
I can use the exception unit there as well and throw out an exception, but the program continues as before. I've used the halt call in the past, but it seems to not do any cleanup or the like so I end up making a big procedure with close and free calls for everything I've done just to be sure (and even then I'm not sure of any of the behind the scenes stuff).
So what is the right way to handle such things?
Edit: To clarify, I'm wanting to know about how to make the program do what clean-up it needs to do and then EXIT NOW and not do any other code.

To perform abnormal termination call Halt() passing the exit code.
if CatastropicErrorDetected then
begin
... show error message
Halt(1);
end;
On Windows this results in a call to TerminateProcess and will stop execution there and then.
You note that no cleanup is performed and usually that's what you want. Since you are performing this check at application startup there should be nothing to cleanup.

IMHO the only clean way would be checking for "Fatal conditions" before Application is running.
program Project2;
uses
Forms,Dialogs,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
ReportMemoryLeaksOnShutDown := true;
Application.Initialize;
if True then // your condition here
begin
MessageDLG('Fatal Error',mtError,[mbok],0);
end
else
begin
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
end.
Any other approach will have side effects
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
FSL:TStringList;
public
Destructor Destroy;override;
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
destructor TForm1.Destroy;
begin
FreeAndNil(FSL);
Showmessage('Will never be seen with Application.Terminate + HALT');
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
const
Testing=0; // try 1 and 2 too
begin
FSL:=TStringList.Create;
Try
raise Exception.Create('Terminating now');
except
case Testing of
0: begin
// exception object will not be freed other code will be prevented, Form won't be shown
Application.Terminate;
HALT;
end;
1: begin
// exception object will not be freed Form won't be shown
HALT;
end;
2: begin
// clean but Form will be shown
Application.Terminate;
end;
end;
end;
end;
end.

You can instruct the application global object to terminate the program by calling Application.Terminate.
Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.
Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.
Since the call can occur deeper in the stack, you can also raise an Exception, and you code your program to not handle it in order to let the execution reach the main application loop and the default exception handler catch it.
That way, you effectively prevent's more code to run in your application.
In code it may look like this:
if not FileExists(FilePath1) then
begin
MessageDlg(FilePath1 + ' does not exist and is required for this program to function.',
mtWarning, [mbOK], 0);
Application.Terminate;
Abort; //raising a EAbort just as an example
end;
Depending on where this code is called, I advise you not to show the message directly, but rather raise an exception with the message and let the application object default HandleException method show the message for you:
if not FileExists(FilePath1) then
begin
Application.Terminate;
raise EMyFatalException.Create(FilePath1
+ ' does not exist and is required for this program to function.');
end;
Which looks more natural to me. EMyFatalException is a hypothetical exception class you can declare and never handle in your except clauses.

You can write your own Application.OnException handler, ex:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure HandleException(Sender: TObject; E: Exception);
end;
var
Form1: TForm1;
type
EMyFatalError = class(Exception);
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.OnException:= HandleException;
raise EMyFatalError.Create('OOPS!');
end;
procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
Application.ShowException(E);
if E is EMyFatalError then
Application.Terminate;
end;
end.

Related

Delphi : thread safely suspend in excute module

I am a beginner of DELPHI.
I have to develop a application based on thread.
How can I suspend thread in thread execute function, not in UI thread....
Please give me sample
Suspend a thread simply by pausing. For instance you can sleep for a specified amount of time. Or you can wait on a synchronisation object. For instance, you might wait on an event. When another thread signals that event, the paused thread will resume.
Assuming you are working on Windows platform...
There are many ways to suspend a thread. Which one to use depends largely on when/why it should resume, and whether or not the thread has a message queue.
The point about the message queue is especially important if you need to suspend a thread for a long time. Any thread not processing Windows message can hang many operations, DDE communications, message broadcast, etc. The contract being "If your thread has a message queue, it NEEDS to treat them.", and it needs to do so in a timely fashion. In this case, I would suggest calling MsgWaitForMultipleObjects from the thread. The function works even if you are not waiting on any objects, which allows you to both wait on messages and have a timeout. WaitMessage could work too, but it doesn't timeout which has, amongst other implication, that you would need to send a message to the thread after terminating it, otherwise, it might never terminate.
If the thread does not have a message queue, then there are plenty of options. Sleep is a valid one if you want to wait for a specific amount of time. If you want to wait for a specific event to resume the thread, the TEvent class might be what you are looking for.
Hard to give a definitive answer without more details.
This example runs OK. By working with threads, by very careful. This is a very difficult section.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyThread=class(tthread)
FEvent:THandle;
private
procedure SetText;
protected
procedure Execute;override;
public
constructor Create(CreateSuspended:Boolean=false);
destructor Destroy; override;
procedure Start;
procedure Stop;
procedure StopAndTerminate;
end;
type
TForm1 = class(TForm)
OutMem: TMemo;
BtnStart: TButton;
BtnStop: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
mythread:tmythread;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
mythread:=tmythread.Create();
end;
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
Inherited Create(CreateSuspended);
FEvent:=CreateEvent(nil,true,false,pchar(inttostr(handle)));
end;
destructor TMyThread.Destroy;
begin
CloseHandle(FEVENT);
inherited;
end;
procedure TMyThread.Execute;
begin
while not terminated do
begin
WaitForSingleObject(FEvent,INFINITE);
if terminated then exit;
Synchronize(SetText);
{ Methods and properties of objects in visual components (forms, buttons, memo and other)
can only be used in a method called using Synchronize}
end;
end;
procedure TMyThread.SetText;
begin
form1.OutMem.Lines.Append(inttostr(gettickcount));
application.ProcessMessages;
// Application.ProcessMessages method must be call ONLY in MainThread.
// by using Synchronize, SetText procedure will be run in MainThread.
end;
procedure TMyThread.Start;
begin
SetEvent(FEVENT);
end;
procedure TMyThread.Stop;
begin
ResetEvent(FEVENT);
end;
procedure TMyThread.StopAndTerminate;
begin
Terminate;
PulseEvent(FEVENT);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
mythread.StopAndTerminate;
mythread.WaitFor;
mythread.Free;
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
mythread.Start;
end;
procedure TForm1.BtnStopClick(Sender: TObject);
begin
mythread.Stop;
end;
end.

Populating an array and displaying its contents into a rich edit on a different form

This is the error I get when I click the btnInfoClick
Debugger Exception Notification
Project_PAT_Phase_3.exe raised exception class EAccessViolation with message 'Access violation at address 004047E0 in module 'Project_PAT_Phase_3.exe' 'Read of address 00000022'.
The program runs smoothly without any errors until I click the button as shown in my code. Please I would appreciate your help.
unit Navigation;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, InfoPopUp;
type
Tvector = Array[1..14] of string;
TFrmNavigation = class(TForm)
btnVote: TButton;
RdgInfo: TRadioGroup;
Label2: TLabel;
btnInfo: TButton;
procedure btnInfoClick(Sender: TObject);
private
public
MyFile : TextFile;
sLine : string;
sArrayParty : Tvector;
end;
var
FrmNavigation: TFrmNavigation;
implementation
procedure TFrmNavigation.btnInfoClick(Sender: TObject);
var
K : integer;
iCheck : integer;
begin
FrmInfo.Visible := true;
K := 1;
iCheck := 0;
if FileExists('PartyInfo.txt') <> True
then
begin
MessageDlg('File does not exist',mtError,[mbOK],0);
Exit;
end;// end of If statement
AssignFile(MyFile,'PartyInfo.txt');
Reset(MyFile);
while NOT eof(MyFile) do
begin
Inc(K);
Readln(MyFile,sLine);
sLine := sArrayParty[K];
end;//end of While
closefile(MyFile);
case RdgInfo.ItemIndex of
0 : begin
FrmInfo.Caption := 'African Christian Democratic Party (ACDP)';
FrmInfo.redOutput.Text := sArrayParty[1];
end;
1 : begin
FrmInfo.Caption := 'African National Congress (ANC)';
FrmInfo.redOutput.Text := sArrayParty[2];
end;
end;
the last end. below is where the error pops up in the code but its in the project unit which is weird cause when i had a breakpoint the exception would stop the program at the while loop.
program PAT_Phase_3;
uses
Forms,
WelcomePage in 'WelcomePage.pas' {frmWP},
Navigation in 'Navigation.pas' {FrmNavigation},
InfoPopUp in 'InfoPopUp.pas' {FrmInfo};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TfrmWP, frmWP);
Application.CreateForm(TFrmNavigation, FrmNavigation);
Application.CreateForm(TFrmInfo, FrmInfo);
Application.Run;
end.
The error message, with an address so close to zero, indicates that you are accessing a nil object reference. Most likely FrmInfo is nil. Or perhaps sArrayParty is nil. Use the debugger to confirm where the error is. Obviously it's an error to refer to a nil reference.
When you get an error like this, use the debugger, configured to break on exceptions, to point you at the line of code that faults. Then try to work out why that line of code fails.
If you are presented with hundreds of lines of code, it's hard to work out where a fault is. If you can concentrate on a single line of code, it's much easier. Time for you to learn to debug.
In Delphi you can set forms to AutoCreate or not via a project setting. If your project is not auto creating all forms, then your FrmInfo is going to be nil. Add this code before your
FrmInfo.Visible := true; line and see if it gets you further.
if FrmInfo = nil then
FrmInfo := TFrmInfo.Create(nil);

Maintain DLL loaded even when process closed

I'm developing a DLL file that will be loaded by my EXE... So the EXE will call the first DLL procedure and when this procedure get loaded I want to keep it openned even if the EXE get closed. The example is, I have a DLL with timer showing a 'Hello World' message.
DLL Code:
uses
SysUtils,
Classes,
Dialogs,
ExtCtrls;
{$R *.res}
type
TMyTimer = Class(TTimer)
public
procedure OnMyTimer(Sender: TObject);
end;
procedure DllMessage; export;
var
MyTimer: TMyTimer;
begin
MyTimer := TMyTimer.Create(nil);
MyTimer.Interval := 10000;
MyTimer.OnTimer := MyTimer.OnMyTimer;
end;
procedure TMyTimer.OnMyTimer(Sender: TObject);
begin
ShowMessage('Hello World');
end;
exports DllMessage;
begin
end.
The EXE is loading like this:
procedure DllMessage; external 'Message.dll'
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
DllMessage;
end;
When I close the EXE I want the DLL keep running and showing the message every 10 seconds... Is that possible?
DLLs are loaded into processes and cannot exist without a process to host them. So what you ask is not possible.
If you want to close your process, but continue to execute code, you will need to start a new and separate process to execute that code.
u need Atach a A DLL to another process,
and hook ur code to execute on your processs !
this method is called Dll Injection and Code Hook,
easy ways using madcodehook component
athttp://www.madshi.net/
example injection
http://help.madshi.net/DllInjecting.htm
example code hooking
http://help.madshi.net/ApiCodeHooking.htm
or
creanting ur ways
http://www.codeproject.com/Articles/4610/Three-Ways-to-Inject-Your-Code-into-Another-Proces

show window before terminate

I have a delphi application that, on startup, checks to see if a process is already running, if it is running, I pass data over to that process and terminate the current process. The problem: In terminating the current process, the window of the app flashes for a split second prior to termination. All the code is in the application initialization, before that main form is even created, so I don't understand how it could show the form for a split second. I have tried numerous things like making the window invisible, nothing seems to work. Is there something I am doing wrong.
You are apparently not terminating soon enough. I'd do something like
program Project1;
uses
Forms,
Windows,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function PrevInstance: boolean;
begin
...
end;
procedure PassData;
begin
...
end;
begin
if PrevInstance then
begin
PassData;
Exit;
end;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Update: I believe you do something like
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure MyInitProc;
begin
if true then Application.Terminate;
end;
initialization
InitProc := #MyInitProc;
end.
This will not work, because Application.Terminate doesn't terminate the application immediately. Instead, it simply posts a WM_QUIT message. This message will be received and acted upon after all initialisation is completed.

Why do I/O errors fail to raise exceptions?

I'm using old style Pascal I/O routines and expect that calls to I/O functions that fail should raise an EInOutError. When I try this I do not see an exception raised and I have no clue why.
procedure TForm1.Button1Click(Sender: TObject);
//var i: integer;
begin
id:=(strtoint(Edit1.Text)-1)*4;
AssignFile(plik,'\klienci\'+linia_klient[id]+'.txt');
try
Reset(plik);
except
on EInOutError do Rewrite(plik);
end;
edit2.Text:=linia_klient[id+1];
edit3.Text:=linia_klient[id+2];
//ListBox1.Clear;
//ListBox1.Items.Add();
end;
Entire code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Edit1: TEdit;
Button1: TButton;
Label2: TLabel;
Label3: TLabel;
Edit2: TEdit;
Edit3: TEdit;
ListBox1: TListBox;
Label4: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
plik:TextFile;
linia_klient,linia_video:array[0..20] of string;
id:integer;
implementation
{$R *.dfm}
{$IOCHECKS ON}
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
Edit1.Text:='Witaj, Podaj ID klienta';
Label1.Caption:='ID';
AssignFile(plik,'klienci.txt');
Reset(plik);
i:=0;
While Not Eof(plik) do
begin
Readln(plik,linia_klient[i]);
inc(i);
end;
CloseFile(plik);
AssignFile(plik,'video.txt');
Reset(plik);
i:=0;
While Not Eof(plik) do
begin
Readln(plik,linia_video[i]);
inc(i);
end;
CloseFile(plik);
end;
procedure TForm1.Button1Click(Sender: TObject);
//var i: integer;
begin
id:=(strtoint(Edit1.Text)-1)*4;
AssignFile(plik,'\klienci\'+linia_klient[id]+'.txt');
try
Reset(plik);
except
on EInOutError do Rewrite(plik);
end;
edit2.Text:=linia_klient[id+1];
edit3.Text:=linia_klient[id+2];
//ListBox1.Clear;
//ListBox1.Items.Add();
end;
end.
The exception EInOutError will only be raised if I/O checking is enabled. To make sure it is enabled do the following:
In your project options, in "Delphi Compiler Options" check the checkbox I/O checking
Remove any {$IOCHECKS OFF} or {$I-} directives from your code as they disable I/O checking
This should give you a proper exception if the file doesn't exist.
Now if (for whatever reason) you cannot enable I/O checking:
With disabled I/O checking you won't get an EInOutError if something goes wrong. Instead you have to check the value of IOResult after every I/O operation. It's like in old Pascal times: If IOResult <> 0 then an error happened.
This (slightly adapted) excerpt from the Delphi docs shows how to work with IOResult:
AssignFile(F, FileName);
{$I-}
Reset(F);
{$I+}
if IOResult = 0 then
begin
MessageDlg('File size in bytes: ' + IntToStr(FileSize(F)),
mtInformation, [mbOk], 0);
CloseFile(F);
end
else
MessageDlg('File access error', mtWarning, [mbOk], 0);
However, nowadays you should use TFileStream to access/create files and don't use the old style Pascal routines anymore. An example how this could look:
filename := '\klienci\'+linia_klient[id]+'.txt';
if not FileExists(filename) then
// "Create a file with the given name. If a file with the given name exists, open the file in write mode."
fs := TFileStream.Create(filename, fmCreate) else
// "Open the file to modify the current contents rather than replace them."
fs := TFileStream.Create(filename, fmOpenReadWrite);
I interpret your question that you would like EInOutError exceptions to be raised whenever a Pascal style I/O function fails. In order to do this you need to enable the I/O checking compiler option.
I/O checking: Enables or disables the automatic code generation that checks the result of a call to an I/O procedure. If an I/O procedure returns a nonzero I/O result when this switch is on, an EInOutError exception is raised (or the program is terminated if exception handling is not enabled). When this switch is off, you must check for I/O errors by calling IOResult.
I guess the code you are working with was written under the assumption that the I/O checking option was enabled, but that you are compiling with it not enabled. Here's a bit of code that demonstrates EInOutError being raised due to an I/O error.
program IOchecking;
{$APPTYPE CONSOLE}
{$IOCHECKS ON}
uses
SysUtils;
var
F: File;
begin
AssignFile(F, 'path/to/file/that/does/not/exist');
Reset(F);//raises EInOutError
end.
I strongly recommend that you enable I/O checking. This will allow you to handle errors using exceptions in a manner consistent with the rest of your code.
Not using I/O checking forces you to check the value of IOResult after every I/O function. This is very error prone (it's easy to forget to check) and results in untidy code.
If you are already running with I/O checking enabled then the most likely explanation for you not seeing an error is that in fact no error is occurring. Perhaps the file does in fact exist.

Resources