I have an application that on startup checks some conditions and launches an external program in the OnShow event of the Main Form. The problem is that if there is an error when launching the external program, I want the application to terminate immediately. But there is an issue with that, in that EurekaLog catches my exceptions and somehow disrupts the message loop there by negating all calls to Application.Teminate and any other normal shutdown methods.
So here is my question, would ExitProcess be the best route then to immediately terminating my application when this condition exists?
By the time OnShow has fired, you're too far into the program to decide that you don't really want the program to run. You should make that determination sooner. OnShow is not the place to decide that the form shouldn't be shown.
This is the sort of thing you should check before you even create the main form. Put your checks in the DPR file, and if you determine that the program shouldn't run, then simply call exit.
begin
Application.Initialize;
if not ApplicationShouldReallyStart then
exit;
Application.CreateForm(TMainAppForm, MainAppForm);
Application.Run;
end.
Fill in your own implementation of ApplicationShouldReallyStart. (And it really should be a separate function, not in-line in the DPR file. The IDE gets confused if the begin-end block in the DPR file gets too complex.)
Aside from that, do not call ExitProcess. Call Halt instead. Halt calls ExitProcess, but it also calls unit finalization sections and other Delphi-specific process-shutdown tasks.
Work WITH the system, not AGAINST it! You can't simply die in the middle of things. If you want to die do it within the rules--WM_CLOSE or maybe your own routine that says why it's dying and then sends a WM_CLOSE.
You better send a wmClose message to the window. Else you have a big chance to get into trouble because of other messages send to the form.
I wrote a small application to test a theory and here is what I would suggest.
Call the CLOSE method.
The following example unit closes the application with no errors in D2009.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
close;
end;
end.
While I fully agree with Rob Kennedy here, I want to note, that you may use EurekaLog's routines to control error dialog behaviour.
For example:
uses
ExceptionLog, ECore;
...
begin
ForceApplicationTermination(tbTerminate);
// ... <- Bad code goes there
end;
That way, the application will be closed right after displaying error dialog.
Related
There was a question similar to this however the user was using something way more advanced so I was quite confused.
This is the procedure the exception flares up. Specifically on the ADOCon.connected line. I am using the dbgo stuff and Microsoft access for my database.
The exception I am getting is: EAcessViolation. I'm wondering what mistake I've made to cause it and how to solve it. I have run the procedure on both with a pre-existing database and a new one. When there is a pre-existing database the exception is one the 19th line and without it is on the 14th line. As a user has mentioned, I have read the documentation however I am still confused on how to solve the error. The error is definitely here as this is the first piece of access I call.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,DB, ADODB,ComObj;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
ADOCom:TADOcommand;
ADOCon:TADOConnection;
ADOQ:TADOQuery;
nameDB:string;
db:OLEVariant;
begin
namedb:='Brill.accdb';
if not fileexists(namedb) then
begin
db:=createOLEObject('ADOX.Catalog');
db.create('Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+nameDB+';');
db:=null;
ADOCon.connectionstring:='Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+
nameDB+';';
ADOCon.connected:=True;
ADOCon.loginprompt:=False;
end
else
ADOCon.connectionstring:='Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+
nameDB+';';
end.
Your code is riddled with errors.
When you declare variables of a particular class type in your code, you're responsible for creating an instance of that class and assigning it to the variable before using it, and cleaning up when you're finished with it.
var
ADOCon: TAdoConnection;
ADOCom:TADOcommand;
ADOQ:TADOQuery;
begin
ADOCon := TADOConnection.Create(nil);
try
// Set up connection properties here
ADOQ := TADOQuery.Create(nil);
try
// Set up ADOQ properties and use query here
finally
ADOQ.Free;
end;
finally
ADOCon.Free;
end;
end;
Also, your use of the db: OleVariant (and all code related to it) is doing absolutely nothing. You get an instance, assign properties to that instance, and then throw it away, which means you can just delete that variable and the three lines of code related to it entirely; they serve zero purpose.
Of course, the better solution than any of the above is to add a TDataModule to your form, drop a TADOConnection and TADOQuery on it, set the properties in the Object Inspector or the OnCreate of the datamodule. You can then move that datamodule into the list of available forms, move it up to be created before your main form, and have access from anywhere in your app that uses that datamodule, and the datamodule will free everything properly when you exit your application. It also separates all of the database code from your user interface, cleaning up your code considerably.
A custom control that I wrote is implicated in a crash when it is destroyed. It is hard to pin down the exact circumstances and it might be a factor that the control is parented by a 3rd party control.
Edit 8 October 2014
I've now got a much better SSCCE that illustrates the crash using only TMediaPlayer (from the Delphi VCL) on TForm. So I've deleted a lot of what I wrote before. Please see the edit history for that. (It turns out that CM_EXIT in the former call stack was a red-herring.)
Here's the SSCCE:
unit Unit1;
interface
uses
System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Menus, Vcl.MPlayer;
type
TForm1 = class(TForm)
MainMenu: TMainMenu;
CrashMenuItem: TMenuItem;
procedure CrashMenuItemClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
fControl : TMediaPlayer;
end;
var
Form1: TForm1;
implementation
uses
Vcl.Dialogs;
{$R *.dfm}
procedure TForm1.CrashMenuItemClick(Sender: TObject);
begin
ShowMessage('Message');
fControl.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
fControl := TMediaPlayer.Create(Form1);
fControl.Parent := Form1;
end;
end.
The call to ShowMessage immediately before freeing the control is crucial.
After dismissing the dialog, the TMediaPlayer control gets a WM_SETFOCUS.
It's destructor is then called. Inherited TCustomControl.Destroy frees the canvas and then inherited TWinControl.Destroy calls TWinControl.RemoveFocus, so it gets a WM_KILLFOCUS.
TMediaPlayer.WMKillFocus calls Paint directly, which tries to use the freed canvas and crashes.
(Previously I had a custom control where CMFocusChanged called Invalidate. The effect was the same but the call stack was rather more involved.)
My original 3 questions, that NGLN has answered below:
Am I doing something wrong merely calling FreeAndNil(fMyControl)? Must I unparent it before destroying it? But this doesn't seem necessary with any other controls, so more likely that will just hide the underlying bug.
Should my control have something in its destructor to fix this so that TWinControl knows not to try to repaint it?
Is there perhaps a bug in the 3rd party parent control? Is it the case that my control should certainly not receive a WM_PRINTCLIENT message once it has started to be destroyed? (The 3rd party control seems to make an explicit call to its inherited TWinControl.Update when it receives CM_EXIT as a consequence of my control losing focus.)
But the real question remains: Is there anything wrong with the code in my SSCCE, or is there a bug in the Delphi VCL?
(Incidentally, the same problem will occur with any descendent of TCustomControl. I used TMediaPlayer for convenience.)
Am I doing something wrong merely calling FreeAndNil(fMyControl)?
No, every control should be able to be freed at any given time, as long as all references to the control are cleared (nilled) and the instance's code isn't run anymore.
Must I unparent it before destroying it? But this doesn't seem necessary with any other controls, so more likely that will just hide the underlying bug.
No, indeed no need to.
Should my control have something in its destructor to fix this so that TWinControl knows not to try to repaint it?
No, normally there is no need to. The VCL has this all build in already. For testing purposes or as a (temporary) workaround, you could try to override PaintWindow with something like if not (csDestroying in ComponentState) then.
Is there perhaps a bug in the 3rd party parent control? Is it the case that my control should certainly not receive a WM_PRINTCLIENT message once it has started to be destroyed? (The 3rd party control seems to make an explicit call to its inherited TWinControl.Update when it receives CM_EXIT as a consequence of my control losing focus.)
The parent control indeed receives CM_EXIT, because it had a focussed control, and now it has not anymore (ie. Form.ActiveControl = nil). So that's normal behaviour. As for why the parent sends a WM_PRINTCLIENT to the control (how do you know that request comes from the parent? It seems to start at the Update call.) I do not know. To rule out the possibility of a buggy parent, retry your case with a different parent.
Update (due to question edit):
TMediaPlayer.WMKillFocus calls Paint directly...
procedure TMediaPlayer.WMKillFocus(var Message: TWMKillFocus);
begin
Paint;
end;
That is taboo! That is definitely a bug in the VCL. Paint should never be called directly other than by a request for painting via a WM_PAINT message. I have submitted a report on QC.
(Previously I had a custom control where CMFocusChanged called Invalidate. The effect was the same but the call stack was rather more involved.)
...
(Incidentally, the same problem will occur with any descendent of TCustomControl. I used TMediaPlayer for convenience.)
That is not the case with a test here in D7 and XE2.
I've had some problems with FastMM false positives. This time, the leaks are in the cases testing forms. It's very similar to the one I described here.
I got a form and some plain old VCL controls in it. The first test run shows leaks which, in fact, doesn't exists. The second run gets no leaks. I've searched over all DUnit source code, but couldn't find the reason in order to fix it. Can somebody help me?
I can't afford to run the test twice because: 1. It will be run in a continuous integration; 2. Some tests really take some time and doubling it wouldn't be wise.
I checked the last 3 options in the DUnit GUI:
- Report memory leak type on Shutdown
- Fail TestCase if memory leaked
- Ignore memory leak in SetUp/TearDown
Here are the sample codes:
// form
type
TForm2 = class(TForm)
button1: TButton;
end;
implementation
{$R *.dfm}
// test
type
TTest = class(TGUITestCase)
private
a: TForm2;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Test;
end;
implementation
procedure TTest.Setup;
begin
a := TForm2.Create(nil);
end;
procedure TTest.TearDown;
begin
FreeAndNil(a);
end;
procedure TTest.Test;
begin
a.Show;
a.close;
end;
You're free'ing the form twice. Setting the action to caFree makes the form free itself automatically.
So either you remove the OnClose method or even better you create the form in the test itself and remove the setup and teardown methods without freeing the form. CaFree will take care of that.
With the downloaded version the situation ocurred ocasionaly. As absurd as it seems, the root cause is: if I hit "F9" too fast after DUnit GUI is loaded, the problem occurs. Waiting few seconds, no leaks are reported.
Since here we overriden the FormShow event to automatically start the tests, in my case the problem always occurred. So, delaying the execution by 2 seconds solved my problem.
Thanks #balazs for helping me out.
I just recently begun using TFrames heavily (OK, yes, I've been living under a rock...). I thought frames supported Message hander method declaration--and I've seen many examples of that. So why does this simple test unit for a TFrame never see the message it posts to itself? (I created the test when I figured out that message handlers weren't being called in my larger application.)
unit JunkFrame;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
const
DO_FORM_INITS = WM_USER + 99;
type
TFrame1 = class(TFrame)
Panel1: TPanel;
private
procedure DoFormInits(var Msg: TMessage); message DO_FORM_INITS;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
{$R *.dfm}
constructor TFrame1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
PostMessage(self.Handle, DO_FORM_INITS, 0, 0);
end;
procedure TFrame1.DoFormInits(var Msg: TMessage);
begin
ShowMessage('In DoFormInits!');
end;
end.
This frame only contains a TPanel, and the frame is used on a simple mainform which contains only the frame and a Close button.
What am I missing?
I see two possibilities:
Your program hasn't started processing messages yet. Posted messages are only processed when your program calls GetMessage or PeekMessage and then DispatchMessage. That occurs inside Application.Run, so if your program hasn't gotten there yet, then it won't process any posted messages.
Your frame's window handle has been destroyed and re-created. Accessing the Handle property forces the frame's window handle to be created, but if the frame's parent hasn't quite stabilized yet, then it might destroy its own window handle and re-create it. That forces all its children to do the same, so the handle you posted the message to doesn't exist by the time your program starts processing messages.
To fix the first problem, just wait. Your program will start processing messages eventually. To fix the second problem, override your frame's CreateWnd method and post the message there. That method gets called after the window handle has been created, so you avoid forcing the handle to be created prematurely. It's still possible for the handle to be destroyed and re-created, though, and CreateWnd will be called each time that happens, so you'll need to be careful since your initialization message might be posted more than once (but never to the same window handle multiple times). Whether that's correct depends on what kind of initialzation you need to do.
The only explanation for this that I can come up with is that your frame's handle is recreated after you post the message and before the message queue is pumped. Try posting in an OnShow.
In some cases I should terminate application with
Application.Terminate;
In that case I want to show some message to user inside destructor of some TFrame.
I have tryed to use MessageBox, MessageBoxIndirect, ShowMessage functions with no success. Message box isn't appears on screen and application closes.
Is there any way to show some message to user while Application terminating?
Btw, Delphi XE used.
Like comments indicate for showing messages with e.g. MessageBox, MessageBoxIndirect or ShowMessage, your process needs to still run.
Delphi for .NET would have a suiting OnShutdown event, but when not compiling with the conditional CLR it is absent.
One can however use an exit procedure, like TApplication does itself with DoneApplication. This procedure is called at a point where the process still lives, before System.Halt is called. It is added by calling AddExitProc(Proc: TProcedure) in System.SysUtils. In code commentary for this is following:
{ AddExitProc adds the given procedure to the run-time library's exit
procedure list. When an application terminates, its exit procedures
are executed in reverse order of definition, i.e. the last procedure
passed to AddExitProc is the first one to get executed upon
termination. }
I would personally decide to use this, despite the warning from the documentation, as TApplication itself is still using it in Tokyo to have DoneApplication called. Excerpt from documentation:
[...]AddExitProc is not compatible with ULX package support and is
provided for backward compatibility only. Do not use AddExitProc in
new applications.[...]
The small code example of a VCL project will show a message on application termination:
program Project1;
uses
Vcl.Forms,
Vcl.Dialogs,
System.SysUtils,
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
procedure AppTerminated;
begin
MessageDlg('Message', mtInformation, [mbOk], 0);
end;
begin
AddExitProc(AppTerminated);
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
Once you have called Application.Terminate any attempt to show a dialog fails. You can't have your cake and eat it. You can't terminate your process, and keep it alive to display a dialog.
So, the obvious solutions to that conundrum are:
Show your dialog before you terminate the application, or
Create a separate process to show the dialog, and then terminate the application.