Jump to content
Incus J

TDataModule OnDestroy event never triggered?

Recommended Posts

I have a TDataModule unit in an FMX app, which is auto-created when the app start up (I'm not creating it manually).  When the app exits, I'd like a cleanup routine to execute.  I've added this in an OnDestroy event handler for the Data Module, like this:

procedure TDataModule1.DataModuleDestroy(Sender: TObject);
begin
  Cleanup;
  end;

But as far as I can tell my Cleanup routine is not called.  So I've placed a breakpoint on the 'Cleanup;' line above.  When I run the app the breakpoint red circle gets a green tick - so I think the breakpoint itself is valid and live.  When I quit the app, it simply closes - the IDE does not stop at the breakpoint.  Does that imply that the Data Module OnDestroy event is never triggered?

 

Do I need to set anything special for the OnDestroy event to be called on an auto-created TDataModule?

 

(I've had a look through the source for TDataModule in System.Classes - there is a call to FOnDestroy in a DoDestroy routine, but I'm not sure of the destruction sequence, as there's also BeforeDestruction and Destroy methods)

Share this post


Link to post

Perhaps the data module isn't being destroyed. If you showed a minimal reproduction then we'd be able to tell. 

Share this post


Link to post
Posted (edited)

OK I'll try - the project file looks like this:

program ProgramName;

uses
  System.StartUpCopy,
  FMX.Forms,
  uiMain in 'Code\uiMain.pas' {MainForm},
  …
  dm1 in 'Code\dm1.pas' {DataModule1: TDataModule},
  …;

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.CreateForm(TDataModule1, DataModule1);
  Application.Run;
end.

So a main form and the data module are created at startup.  That's it really.  I'm not manually freeing the data module - I think it is owned by the Application, so will be freed automatically when the Application is terminated (?)

 

The data module itself has two event handlers assigned:

OnCreate	DataModuleCreate
OnDestroy	DataModuleDestroy

The OnCreate (DataModuleCreate) is working OK.  The code in DataModuleDestroy is shown in my initial post above.  I can work around it by calling my CleanUp routine via the main form's OnClose event instead, but puzzled.

 

Edited by Incus J

Share this post


Link to post

2 basics that might help take this thread further.

 

What platform are you debugging on?

Just to be sure, are you compiling in Debug not Release?

Share this post


Link to post

Thanks SwiftExpat - the app is running on macOS (deployed by the IDE when I click run).  The IDE is running in a Windows VM on the same system (Fusion).  Yes, compiling is set to Debug - and I can place breakpoints in other methods, such as DataModuleCreate, and that works OK - the app pauses at that point.  So breakpoints in general are working.  But when I place a breakpoint in DataModuleDestroy - and then Exit, the app simply closes without pausing at the breakpoint.  The breakpoint marker gets a green tick when the app is run, which I think is an indication of a valid breakpoint.

 

So my guess is the DataModuleDestroy procedure is not entered during Exit - so perhaps the associated OnDestroy event isn't occurring (speculation).  I posted thinking I must have missed something obvious - and that may well be the case.  I'll try setting up the same thing in a new empty project next, see whether I encounter the same behaviour again.

Share this post


Link to post

On MacOS the process is killed, so the app never gets to shutdown correctly.  I opened a support ticket with Embarcadero and was not given a resolution to this.

 

I ended up with a timer to save state every x seconds 😞  Maybe someone else has a better solution.

  • Thanks 1

Share this post


Link to post

Instant termination sounds like a (fairly major?) bug.  If Exiting an app simply kills the process, nothing will get freed, preferences won't save etc. - so I'd need to free everything manually in the main form's Close event perhaps.

 

Did you submit a bug report on the quality site?  If yes, and you have a link to the report I can vote for it I think.

Share this post


Link to post

I thought about this one quite hard today, a bit of fun learning, and here is my suggestion.  FMX has the following mapped:

procedure TPlatformCocoa.Terminate;
begin
  FRunning := False;
  FTerminating := True;
  TMessageManager.DefaultManager.SendMessage(nil, TApplicationTerminatingMessage.Create);
  NSApp.terminate(nil);
end;

So in my app I implemented it as this , similar to what they recommend for android.

unit dmDestroyMe;

interface

uses
  System.SysUtils, System.Classes, System.Messaging;

type
  TDestroyVersion = class
  public
    VersionString: string;
    constructor Create;
    destructor Destroy; override;
  end;

  TDestroyMeDM = class(TDataModule)
    procedure DataModuleCreate(Sender: TObject);
    procedure DataModuleDestroy(Sender: TObject);
  private
    FVersion1, FVersion2: TDestroyVersion;
    v1, v2: string;
    msgSubId: integer;
    procedure ApplicationTerminatingHandler(const Sender: TObject; const Msg: TMessage);
  public
    { Public declarations }
  end;

var
  DestroyMeDM: TDestroyMeDM;

implementation

{%CLASSGROUP 'FMX.Controls.TControl'}
{$R *.dfm}
{ TDestroyVersion }

uses FMX.Forms;

constructor TDestroyVersion.Create;
begin
  VersionString := '1.2.3.4';
end;

destructor TDestroyVersion.Destroy;
begin
  VersionString := '0.0.0.0';
  inherited;
end;

procedure TDestroyMeDM.ApplicationTerminatingHandler(const Sender: TObject; const Msg: TMessage);
begin
  v1 := '0.0.1.0';
  Fversion1.Free;
  v2 := '0.1.0.0';
end;

procedure TDestroyMeDM.DataModuleCreate(Sender: TObject);
begin
  v1 := '2.0.1.0';
  v2 := '3.1.0.0';
  FVersion1 := TDestroyVersion.Create;
  FVersion2 := TDestroyVersion.Create;
  msgSubId := TMessageManager.DefaultManager.SubscribeToMessage(TApplicationTerminatingMessage,
    ApplicationTerminatingHandler);
end;

procedure TDestroyMeDM.DataModuleDestroy(Sender: TObject);
begin
  FVersion2.Free;
  TMessageManager.DefaultManager.Unsubscribe(TApplicationTerminatingMessage, msgSubId, true);
end;

end.

I will keep looking for the QP issue,  but honestly this is one they expect you to implement.  I just did not have the experience when I saw this the first time around.

DestroyMe.zip

  • Thanks 1

Share this post


Link to post

Thank you!  It looks a little bit over my head at first glance - but I will have a good look through it.  Perhaps I also need to have a look at the equivalent TPlatformWindows Terminate procedure and compare with the macOS Cocoa one to see how they differ.

 

Naive question:  If they expect you to implement the form destruction event manually, what is the actual purpose of the OnDestroy event property in the object inspector?

Share this post


Link to post
13 minutes ago, Incus J said:

Naive question:  If they expect you to implement the form destruction event manually, what is the actual purpose of the OnDestroy event property in the object inspector?

It's a bit different when the form is destroyed while application is running and when the application is closed / terminated. (The OS should/will clean up anything that is not freed by application.)

Share this post


Link to post
9 hours ago, Incus J said:

It looks a little bit over my head at first glance

Many of those lines are just there just to be able to place break points and verify what lines are actually executed (or not executed ) when the application terminates.

 

9 hours ago, Incus J said:

what is the actual purpose of the OnDestroy event property in the object inspector

Think of the situation where you need a child window, when that window is destroyed ( destroyed and closed are not the same ). In the parent window you would call free on the child window to have the destructor called.

 

 

Share this post


Link to post

OK, I think I've understood (hopefully :).  If I free a form or data module manually in code, then its associated OnDestroy event fires.  But if the user closes the application, then OnDestroy events for auto-created forms and data modules are bypassed: The application simply terminates and relies on the OS to clean up any allocated memory.

 

On 6/20/2022 at 6:12 PM, SwiftExpat said:

I ended up with a timer to save state every x seconds 😞  Maybe someone else has a better solution.

I've noticed that a form (but not a data module) has an OnSaveState event in the object inspector.  Documentation indicates it is fired when a form is about to close.  If that completes before the app terminates, it might be a good place to save preferences/state etc. and clean up.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×