-2

I'm backing up a database using Devart controls. Everything runs fine until I try to free the form having the backup component(TDump) and a progress bar on it. I moved the call to the finally portion of code and checked if it was assigned before trying to free it still same problem

procedure TfrmMain.CmdBackupExecute(Sender: TObject);
var
    SaveDialog: TSaveDialog;
    QRYString: String;
    frmBackup: TfrmBackup;
    Password: String;
    BackupPassword: String;
    MasterPassword: String;
    CurrentFrame: TFrameType;
    OldUser: String;
    OldPassword: String;
begin
    dmVintage.tblSettings.Open;
    BackupPassword:= dmVintage.tblSettings.FieldByName('BackupRestorePWord').AsString;
    MasterPassword:= dmVintage.tblSettings.FieldByName('MasterPWord').AsString;
    InputPassword('Enter Backup Password', Password);
        if Password = BackupPassword then
            try
                try
                    //close current frame and change to root
                    CurrentFrame:= FrameManager.CurrentFrameType;
                    FrameManager.Clear;
                    dmVintage.connMain.LoginPrompt:= False;
                    OldUser:= dmVintage.connMain.Username;
                    OldPassword:= dmVintage.connMain.Password;
                    dmVintage.connMain.Connected:= False;
                    dmVintage.connMain.Username:= 'root';
                    dmVintage.connMain.Password:= MasterPassword;
                    dmVintage.connMain.Connect;
                    SaveDialog:= TsaveDialog.Create(frmMain);
                    SaveDialog.Filter := 'SQL file|*.sql';
                    SaveDialog.DefaultExt:= '.sql';
                    SaveDialog.FileName:= 'VintageData';
                    if SaveDialog.Execute then
                        begin
                         frmBackup:= TfrmBackup.Create(frmMain);
                         frmBackup.Show;
                         frmBackup.mdVintage.BackupToFile(AddTimestampToFilename(SaveDialog.FileName), QryString);
                         //FreeAndNil(frmBackup);
                         //FreeAndNil(SaveDialog);
                         dlgI('Backup Seccessful');
                        end;
                Except on E: Exception do
                    dlgW2('TfrmMain.CmdBackupExecute', E.Message);
                  end;
            finally
                //ShowMessage('Finally');
                if Assigned(frmBackup) then
                  FreeAndNil(frmBackup);
                if Assigned(SaveDialog) then
                  FreeAndNil(SaveDialog);

                //reset connection and load old frame
                dmVintage.connMain.Connected:= False;
                dmVintage.connMain.Username:= OldUser;
                dmVintage.connMain.Password:= OldPassword;
                dmVintage.connMain.Connect;
                dmVintage.connMain.LoginPrompt:= True;
                FrameManager.LoadFrame(CurrentFrame);
            end
        else
          dlgE('Invalid Backup Password');
end;

function TfrmMain.AddTimestampToFilename(Value: String): String;
var
  Extension: String;
  FileName: String;
  FormattedDataTime: String;
begin
  Extension:= ExtractFileExt(Value);
  FileName:= ChangeFileExt(Value, '');
  DateTimeToString(FormattedDataTime, 'yyyymmdd_hhmm', Now);
  FileName:= FileName + '_' + FormattedDataTime;
  Result:= ChangeFileExt(FileName, Extension);
end;

The Backup form is very simple with a few labels the TDump component and a progress bar.

unit uBackup;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, uDataVintage, DADump, MyDump,
  Vcl.ComCtrls, Vcl.StdCtrls;

type
  TfrmBackup = class(TForm)
    mdVintage: TMyDump;
    lblBackingUpTable: TLabel;
    lblTable: TLabel;
    Label3: TLabel;
    pbBackup: TProgressBar;
    procedure mdVintageBackupProgress(Sender: TObject; ObjectName: string;
      ObjectNum, ObjectCount, Percent: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;



implementation

{$R *.dfm}

procedure TfrmBackup.mdVintageBackupProgress(Sender: TObject;
  ObjectName: string; ObjectNum, ObjectCount, Percent: Integer);
begin
  Application.ProcessMessages;
  if lblTable.Caption <> ObjectName then
    lblTable.Caption:= ObjectName;
  pbBackup.Position:= Percent;
end;

end.
Gary Shelton
  • 133
  • 1
  • 1
  • 9
  • 4
    Did you try to "close" frmBackup before destroying it? – Sertac Akyuz Feb 18 '19 at 14:19
  • 1
    Just a tip. If you manage the modal forms in a local method, use `SomeForm := TSomeForm.Create(nil)`. This means that you will take the responsibility to free the form. – LU RD Feb 18 '19 at 14:36
  • Tried closing the form first no difference. debugger takes me here in the vcl.controls unit procedure TControl.SetBoundsRect(const Rect: TRect); begin with Rect do SetBounds(Left, Top, Right - Left, Bottom - Top); end; – Gary Shelton Feb 18 '19 at 14:48
  • Doing anything with the Form Close, Hide caused the violation. I placed the calls right after the call to Backup and before the completion dialog and both caused a violation – Gary Shelton Feb 18 '19 at 14:54
  • 1
    `Show` will immediately return after setting the form visible and program flow will continue opposed to `ShowModal` which will return when the form closes. This could potentially cause your issue as you are then immediately trying to `Free` the form. Additionally in my experience using `Application.ProcessMessages` brings nothing but woes, it is most often a sign of something not working and using the wrong tool to solve that. – nil Feb 18 '19 at 15:15
  • @Nil: Amen to that, about A.PM. – MartynA Feb 18 '19 at 15:42
  • Presumably TMyDump is descended from TDump? Does TDump have an event or other mechanism to indicate that it is complete? If so, are you making use of it? And what do Devart say, seeing as its their component which seems closest to the problem? – MartynA Feb 18 '19 at 16:36
  • I could not find anything in the documentation that would notify when done. I did post on Devart's forum so hopefully they will respond. The ProcessMessages is because even with a small database now there are several tables to interate through as well as data and BackupProgress is called repeatedly so you can update a progress bar. Right now it produces a file with 9000+ lines. How else would I keep the program from freezing while the backup is performed? Threads? – Gary Shelton Feb 18 '19 at 18:29
  • ">Threads?". Yes. – LU RD Feb 18 '19 at 18:56
  • Noooooo!! I was afraid of that. I'm too new for them. It looks like I'll be hitting the books. I did start an older CodeRage video on OmniThread knowing it was coming to this. – Gary Shelton Feb 18 '19 at 19:18
  • Found the problem. The settings table was opened and not closed. Thanks for all the help – Gary Shelton Feb 19 '19 at 18:16

1 Answers1

1

Use frmBackup.Release when freeing (especially non-modal) forms.

Freeing forms

The form is not shown in a modal way, and you couldn't, because you're controlling it from the main form, which wouldn't work on a modal form.

I think you get the access violation because you 'bluntly' free the form, while the form itself is also still visible and handling messages. Because of that, the form code (the general TForm code) might at some point still try to do something to the form, even though your instance has already been cleaned up. Even when you call 'Close` in the code, you have this issue, because closing is also not a synchronous process, and requires the form to handle messages.

In general the solution is to call frmBackup.Release instead of frmBackup.Free. That way, the form queues a message for itself. It will first handle the other stuff it has to do, and at some point encounter this message and start a graceful clean-up procedure before it finally frees itself. This is typically the way to close a form from, say, a button-click event on the form itself, but I think it will get you out of this pickle as well.

General tips on Free and FreeAndNil

You don't need to call FreeAndNil in most cases, and especially not on a local variable for which you know exactly when it was assigned a value or not. The only thing FreeAndNil does, is make your reference nil, which is not needed at all for a variable that goes out of scope three lines later anyway.

There is no need at all to call if assigned before calling FreeAndNil, or even Free. Assigned is only checking if the reference is nil, which is what Free also does internally. That right: This is valid code that won't throw errors:

var
  o: TObject;
begin
  o := nil;
  o.Free;
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • That sure sounded like it should do the trick, but no joy. I will use that in the future though. Thank's for the tip. I use the form to give the user a progress bar since this takes a bit. I will try moving the TDump component to the datamodule since I think it's the culprit. – Gary Shelton Feb 18 '19 at 15:02
  • if I comment out the call to Backup everything works. Could it be the process is still working when I try to close/Free the form? Shouldn't Release keep this from happening? – Gary Shelton Feb 18 '19 at 15:31
  • 1
    Could you remove `Application.ProcessMessages;` from that progress event handler, and instead call `lblTable.Repaint` after updating its caption? Application.ProcessMessage is quite dangerous, since it actually forces the application to process messages 'before their time'. This can have all kinds of side effects, for example that a close message is processed before the event handler is done.. – GolezTrol Feb 18 '19 at 15:41
  • I don't know if the dump itself could still be working after the call returns to the main form. If that component does its work in a background thread, then that's definitely possible, but I don't know TMyDump, so couldn't tell if it's the case. – GolezTrol Feb 18 '19 at 17:08
  • I changed Application.ProcessMessages to repaint, sounds like everyone hates that. I think the problem is in the Dump component. When moving calls around I found that the cursor is changed while processing and even though the lblTable shows it iterates through all tables and views and the 9000+ SQLFile is created with no problem, the cursor will remain changed as if the process is still working. I will try Devarts forum as well Thank you – Gary Shelton Feb 18 '19 at 17:13
  • [About Application.ProcessMessages on SO](https://stackoverflow.com/questions/25181713/i-do-not-understand-what-application-processmessages-in-delphi-is-doing), with two answer describing what it does and what its pitfalls are. – GolezTrol Feb 19 '19 at 07:13