I have an application written in Delphi that uses an iSeries ODBC connection.
There are some workstations where I do not want to install the iSeries software, and on these workstations, I won't be updating any of these databases anyway.
Is there a way I can trap when this error message is generated? At that point, I can just set a variable like NoUpload to true and not allow the connection on the workstation.
It appears to happen before I ever attempt to even open one of the tables - just by having the ConnectionString set when the application starts fires the message.
Thanks in advance!
You can check the existing ADO providers of the system with ADODB.GetProviderNames
Ideally, you should look for an option to check your condition without an exception being raised. So Sir Rufo's answer is a good place to start.
Another option might be to not include the Provider in the ConnectionString, but set it independently via the Provider property at run-time (most likely only after confirming that it's supported).
However, since you mentioned you're getting an exception before you even attempt to open a table, there are a few things to check (assuming you've been setting up your components at design time):
Have any data sets accidentally been left Active at design time?
Has the Connection been left active at design time?
Are there any options in the ConnectionString that could immediately trigger the error?
Failing the above you could provide a hook for application exceptions. (And really more of a last ditch effort.)
Declare a handler method using with the following signature: TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;. And assign it to Application.OnException. E.g.
procedure Handle(ASender: TObject; E: Exception);
begin
if ISeriesNotInstalledError(E) then
begin
FNoUpload := True;
end
else
begin
Application.ShowException(E);
end;
end;
NOTE: There are some important considerations in following this approach. Since you see this as a standard Use Case, you don't want to be bothering your users with messages. This is also much better than a localised exception handler (a common programming error) because if a caller routine triggers this error you don't want the caller to mistakenly run as if nothing went wrong; when quite clearly something did.
Related
I use a function to log application errors to a file.I have it on my main form.How do I tell my other forms, in case of an error,to use this function? Just by chance I added this to my query on some other form:
......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);
....
My LogError(E); is not recognized (gives error). Tried adding the function :procedure LogError(E:Exception); to public uses but wont work.
Structured Exception Handling
I'd like to point out that your approach to exception handling is very tedious and wasteful coding. Not to mention quite dangerous. You don't want to be littering your code with individual handlers to log your exceptions when there's a much more practical approach.
Fortunately Delphi provides the means to make this much easier. First a little background about structured exception handling... Most developers make the mistake of trying to deal with errors by writing code like the following:
try
//Do Something
except
//Show Error
//Log Error
end;
The problem with the above code is that it swallows the error. Even though the user sees an error message, the error itself is hidden from the rest of the program. This can result in other parts of the program doing things they should not do because of the error.
Imagine a program that calls the following routines: LoadAccount; ApplyDiscount; ProcessPayment;. But the the first routine raises an error. Which a programmer (mistakenly thinking they're being diligent) decided to "handle" as above. The problem is that the next two routines will still be called as if nothing is wrong! This could mean that a discount is applied to and payment processed for the wrong account!
The point is that structured exception handling saves us from these headaches (provided we don't break the way it works). If the LoadAccount routine didn't try "handle" the exception, then while the application is in an "exception state" the code would simply keep jumping out of routines until it finds a finally / except handler.
This suits an event driven program very nicely.
Suppose a user clicks a button that will cause your program to start a multi-step task: with loops, calls to child methods, creating objects etc. If anything goes wrong, you want to abandon the entire task and wait for the next input. You don't want to stubbornly keep trying to complete the task; because you'll either just get more errors, or worse: later changes will do the wrong thing because earlier required steps did not complete successfully.
Easy Logging of Errors
As mentioned earlier, if you simply leave an exception alone, the error will "bubble up" to an outer exception handler. And in a standard GUI application, that will be the default Application exception handler.
In fact at this stage, doing nothing special: if an exception is raised in the middle of the button click task described earlier, the default exception handler will show an error message to the user. The only thing missing is the logging. Fortunately, Delphi makes it easy for you to intercept the default Application exception handler. So you can provide your own implementation, and log the error as you desire.
All you need to do is:
Write a method using the TExceptionEvent signature.
E.g. procedure MyHandleException(ASender: TObject; E: Exception);.
Then assign your custom handler using: Application.OnException := MyHandleException;
General Logging
Of course, this only covers logging of exceptions. If you want to do any other ad-hoc logging, you want to be able to reuse the code. This basically requires you to move your logging code into a separate unit that all your other units can call as needed.
So putting these things together you might do something as follows:
TLogger = class
public
procedure WriteToLog(S: string); //Performs actual logging of given string
procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;
Then you might set it up in your program unit as follows:
begin
Logger := TLogger.Create(...);
Application.OnException := Logger.HandleException;
Logger.WriteToLog('Custom exception handler has been assigned.');
//Rest of application startup code
end;
Final Thoughts
I mentioned that if an unexpected exception occurs, you want to abandon the current task. But what happens if you've already done some things that should now be undone as a result of the error. Then you would simply implement it as follows:
try
//Do something
except
//Undo
raise; //NOTE: more often than not, one should re-raise an
//exception so that callers are aware of the error.
end;
Note that this is the primary use for exception handlers. Yes they can also be used to swallow exceptions. But they should only swallow an exception if it has been truly resolved.
While Delphi is object-oriented language, it can deal with non-OO entities as well.
Think, your main form is property of which form ?
The answer is that main form is not the property of ANY other form, it is a global object. Same exactly thing should be done about your function.
unit Main;
interface uses .......;
type TMainForm = class (TForm)....
....
end;
Procedure LogError(const message: string);
implementation uses .....;
Procedure LogError(const message: string);
begin
...
end;
(**********)
procedure TMainForm.FormCreate(...);
begin
....
end;
...
end.
Now even better option would be to totally decouple LogError from ANY form at all.
Unit MyLogger;
interface uses ....; // but does not uses Main or any other form
procedure SetupLog(....);
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.
// just keep it extensible
Procedure LogWarning(const message: string);
Procedure LogError(const message: string);
implementation uses ...;
function GenericLoggingSink(.....);
begin
...
end;
Procedure LogError(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;
Procedure LogWarning(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;
And your Main form should just use this unit and this function on the same terms as all other forms in your application.
As part three I suggest you think what you want from your logging procedures.
Actually doing this only makes sense for very simplistic logging needs. Very simplistic.
Otherwise just take any existing Delphi logging frameworks and utilize a lot of functionality implemented there. For example can you just log an object of any class you would write?
The only reason to make your own logging library is "my program is so simplistic, that it does need only the most primitive logging. It would be faster to improvise my own system, than to copy-paste library initialization and setup from examples to a ready-made libraries".
Log4Delphi and Log4D are well-known FLOSS libraries, though they look somewhat abandoned. Maybe there just is nothing left to be extended. Those are most old ones, but there also are some newer FLOSS libraries, an easy example being http://blog.synopse.info/post/2013/02/03/Log-to-the-console
If anything, you can read their texts and learn from them.
There are also commercial libraries like those listed at Is there a way to do normal logging with EureakLog? and Delphi itself is shipped with a limited version of CodeSite logging framework.
Delphi IDE enhancements like CnWizards and GExperts also do come with a simplified debug-oriented logging interface.
Google would bring you even more options if you'd dare :-)
The problem is that as my first executable statements I want to check if I can read from a databse. If I can't, I call MessageDlg to explain so, then I Halt;.
However, after closing the dialog, I still see the application in the tak manager (and if I stop it and re-run the application, the same thing occurs).
Any idea what I am doing wrong?
Global.ADQuery1 is an AnyDac database access component. I access the d/b by IP address. The code works fine when I set my PCs address to the d/b address and gives the reported problem when I change my IP address (hence, can't access the d/b, which throws an exception).
procedure TMainForm.FormCreate(Sender: TObject);
begin
try
Global.ADQuery1.Open('SHOW DATABASES');
except
On E: Exception do
begin
MessageDlg('Database access problem', mtError, [mbOK], 0);
Halt;
end;
end;
[update] when I run in the IDE, after catching
(EMySQLNativeException) : "[AnyDAC][Phys][MySQL] Can't connect to MySQL server on '10.21.18.211' (10060)"
I catch an EIdWinSockStubError either the program has not called wsastartup or wsastartup failed - but I don't udnertsand how it is thrown ... I guess that Application.Terminate calls may main form's FormClose, which doesn't do anything with my Indy components, but I guess that when the parent form is destroyed then its children will be too.
[further update]
My TMainForm.FormCreate now says only
Sleep(1000);
PostMessage(Handle, UM_PROGRAM_START, 0, 0);
And I moved all the code into the stat of function that handles that. Surely everything is created at that time? So, why does my Indy component throw an exception?
Maybe I should put the PostMessage() in my [application].pas after Application.Run(); ?
(Aside: 1) how do others generally handle application start in this way? 2) does anyone have an application skeleton? I was thinking of creating one with options to handle minimize to system tray, only allow one instance, recent files menu, etc, etc) - although that might be better as a separate question
The Halt procedure is not the immediate process-killer we sometimes mistake it for. It calls the unit-finalization sections of all your program's units, so your program might be stuck in one of those, perhaps waiting for something to happen to your form, which isn't going to happen since your OnCreate handler hasn't returned yet.
You could use the debugger to find out what your program is doing or waiting for.
To really get out of a program as fast as possible, skip Halt and go straight to ExitProcess. That's the final thing Halt calls.
Application.Terminate is actually farther from the point where any real termination occurs since it's really just an advisory command; the application won't terminate until it reaches the message loop.
Better yet, find a more graceful way to exit your program. For example, test your database before creating your form so you're not left in the awkward position of having a half-created form that you don't really want anymore.
I want to disable the exception catching by Delphi and let Windows catch it - making it produce a window like "AppName crashed. Debug , Send", add this to Application events, create a memory dump and so on.
By default, Delphi catches all the exception in TApplication.Run procedure... How can I avoid that without modifying Forms.pas?
You could add an OnException handler that re-raised the exception:
class procedure TMainForm.OnException(Sender: TObject; E: Exception);
begin
raise Exception(AcquireExceptionObject);
end;
initialization
Application.OnException := TMainForm.OnException;
I'm not sure why you would want to do this at all though. It's more normal to use a tool like madExcept or EurekaLog to show an error dialog that yields much more helpful information than the system dialog.
You can set JITEnable to '1' or higher (default is '0'). With '1', non native exceptions, with higher than '1', all exceptions will be handled by JIT or WER (depending on the system).
This may not be what you want though. With this solution any qualifying exception will be passed to the OS, it doesn't matter if they're handled in code or not. Clarification (run outside the debugger):
procedure TForm1.Button1Click(Sender: TObject);
begin
raise EAccessViolation.Create('access denied');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
try
PInteger(0)^ := 0;
except
end;
end;
initialization
JITEnable := 1;
The first example is a native exception, it will be handled by the application exception handling mechanism when JITEnable is 1. But the second example will trigger JIT/WER.
Add your own handler. Application.OnException is probably what you want. Better than leaving it up to windows as well, as you get different behaviours depending on the environment. For instance if windows error reporting is on, it will ask the user if they want to send an error report to MS.
Like Mr Heffernan I recommend you look at something like EurekaLog.
AS. I agree with voices above that this wish is rather strange.
I also agree that practically hooking in TApplication.OnException would probably be enough ("if it looks like a duck...")
However if you truly want to make RTL oblivious to exceptions, there are ways too.
Exception handlers are plugin to low-level RTL, just like heap management, etc.
You can look at KOL (Key Objects Library).
In Delphi 5 times i managed to make 2KB-size DLL.
That required absense of many usualyl taken "for granted" features. Exception were among them.
To enable Exceptions in KOL's system RTL replacement, you had to make some $DEFINE's, and then the code to add exceptions support to IDE was unlocked.
I believe you can still get that modularized RTL version and grep for that $IfDef and see which code is replaced with which.
I believe there is fair chance you can undo that and make Windows avoid calling Delphi RTL over Exceptions.
I don't remember details, but i believe Delphi RTL Exception handler is just registered in Windows core as a callback. And you probably can de-register it (register nil callback).
I believe you can find it in stock RTL, but KOL's modularised RTL would just make it easier to search.
I wonder whether in Delphi calling
Query1.Unprepare;
implicitly closes Query1, if it was previously active. Such that e.g. calling Next on it will fail.
You might say, just go ahead and try but I did on a 64-bit Windows 7 system and had all sort of problems with it until finally my BDE Administrator seems to be completely broken. So I decided to just ask this questions before I start to find out, how I can get BDE running on my system ;-)
You can not use Prepare/Unprepare on an open dataset. you need to close it first.
unit DBTables;
...
procedure TQuery.SetPrepared(Value: Boolean);
begin
if Handle <> nil then DatabaseError(SDataSetOpen, Self);
...
// SDataSetOpen = 'Cannot perform this operation on an open dataset';
Can someone enlighten me on handling the database connection (and errors) using try finally ?
What would be the best practice ?
Seen various styles but I wonder what would be the best approach.
Should opening of the tables be put in TRY block or just the main connection
string ?
Since I usually put my database (absolute database,access..) in my exe folder
I was wondering about the best approach on this...
Or first check for file like ...
if (FileExists(sDatabasePath)) then begin
ADOConnection1.ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+sDatabasePath+';Persist Security Info=False';
try
ADOConnection1.Connected:=True;
ADOTable1.Open;
except
ShowMessage ('cant access the database !');
end;
end;
???
Comments:
Never swallow exceptions, like you essentially do in your ShowMessage case. The code calling your procedure would have no way of knowing something went wrong. Only handle errors if you can fix them, or let them bubble-up the application error handler, where they'll be displayed for the user.
Depending on how your code works, you might want to protect the connection to the database with a try-finally so you're disconnected once the job is done. I don't do that, I usually keep the connection open for the life of the application.
Depending on what you do with the ADOTable1 you might want to make sure it gets closed once you're done using it with an other try-finally block. I usually do that because I don't use Db aware GUI controls and I'm a control-freak. I also handle the transaction manually (start transaction / commit / rollback)
Don't ignore errors. if your database doesn't exist (ie: FileExists() returns false), code calling your procedure doesn't know a thing, nor does the user.
Here's how I'd re-write your code:
if (FileExists(sDatabasePath)) then
begin
ADOConnection1.ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+sDatabasePath+';Persist Security Info=False';
ADOConnection1.Connected:=True;
try
ADOTable1.Open;
try
// Do non-GUI database stuff here.
finally ADOTabel1.Close;
end;
finally ADOConnection1.Connected := False;
end;
end
else
raise Exception.Create('Database file not found');
If I cannot open the database I terminate the application - not much you can do without database access unless you specifically build an architecture that handles this.
Other than this, just let the default application error handler handle the error, since it would be pretty unexpected anyway.