14

I need to intercept Windows shutdown, and execute some DB query, before that my application will close. I'm using Delphi XE10 under Windows 10 on a FMX project

What I tried is the code below, but it doesn't work

  private
    { Private declarations }
  {$IFDEF MSWINDOWS}
    procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
    procedure WMEndSession(var Msg : TWMQueryEndSession); message  WM_ENDSESSION ;
  {$ENDIF}

  end;



procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
var
 lista:TStringList;
begin

{$IFDEF MSWINDOWS}
  try
    lista:=TStringList.Create;
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' event WMQueryEndSession');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
    SincroClose();
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
  finally
    lista.Free;
  end;
{$ENDIF}
  inherited;

end;



procedure TfMain.WMEndSession(var Msg: TWMQueryEndSession);
var
 lista:TStringList;
begin

{$IFDEF MSWINDOWS}
  try
    lista:=TStringList.Create;
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' WMEndSession');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
    SincroClose();
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
  finally
    lista.Free;
  end;
{$ENDIF}
  inherited;

end;



procedure TfMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var lista:TStringList;
begin

{$IFDEF MSWINDOWS}
  CanClose:=false;
  try
    lista:=TStringList.Create;
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' FormCloseQuery');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
    SincroClose();
    lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
    Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
    CanClose:=true;
  finally
    lista.Free;
  end;
{$ENDIF}

end;

Only normal closing application, will work fine ,under FormCloseQuery event, but when Windows is shutting down, My application will close without saving any data

Sebastian Z
  • 4,520
  • 1
  • 15
  • 30
Gianluca Colombo
  • 717
  • 17
  • 38
  • @David Hefferman **N.B. This question in not a duplicate of [Delphi prevent application shutdown][1] What I don't understand is why my application cannot intercept WM_QUERYENDSESSION or WM_ENDSESSION** – Gianluca Colombo Oct 01 '16 at 14:38
  • Start with code that is known to work, which you have, and work from there. Also, your lifetime management is broken. Acquire before try. – David Heffernan Oct 01 '16 at 15:57
  • On Vista and later, use [`ShutdownBlockReasonCreate()`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa376877.aspx), this is discussed in [Application Shutdown Changes in Windows Vista](https://msdn.microsoft.com/en-us/library/ms700677.aspx). Windows shutdown is not a good time to perform DB queries. What if the DB engine were shutdown before your app? In any case, please provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) proving you are not receiving the messages. Also try using `TApplication.HookMainWindow()` to receive them. – Remy Lebeau Oct 01 '16 at 17:01
  • So now we should close the question for not offering a MCVE.... – David Heffernan Oct 01 '16 at 18:00
  • I forgot to say that the project is not a VCL project but a multi device application FMX – Gianluca Colombo Oct 01 '16 at 22:25
  • 1
    Message handlers are for VCL. Start [here](http://docwiki.embarcadero.com/RADStudio/en/Using_Messages). – Sertac Akyuz Oct 02 '16 at 00:07
  • Oh. It's an FMX project! Now you tell us. – David Heffernan Oct 02 '16 at 07:22
  • I'm sorry, you are right, but what did you think when you read "{$IFDEF MSWINDOWS}" in my code?? A Vcl project?? OMG – Gianluca Colombo Oct 02 '16 at 09:25
  • So you are going to need to handle messages in a visible top level window. That's going to mean doing the following: 1. Override CreateHandle and DestroyHandle to sub-class the window procedure. That's also going to require use of `WindowHandleToPlatform` to get the `HWND` created in `CreateHandle`. 2. In your sub-classed winproc handle the necessary messages. 3. You may also need to call `ShutdownBlockReasonCreate`. 4. As Remy said, DB access is probably a bad idea during shutdown. – David Heffernan Oct 03 '16 at 16:03
  • 1
    The key though is to be able to receive those messages. Your main form is a visible top level window. You cannot hook its winproc using FMX methods. You can obtain the HWND though. And then you can use standard winapi hooking. – David Heffernan Oct 03 '16 at 16:03

3 Answers3

15

FormCloseQuery works because it's exposed by the framework. Your application does not save any data when Windows is shutting down because your message handlers are never called. Message handling is only available to VCL applications, fmx applications have a different mechanism for messaging as documented.

Brief explanation here implies that it is possible to receive notifications from the OS in fmx framework. However I don't know if this includes shutdown notifications and if it is possible to set your return, as the documentation mentions the message object to be read only.

Until you find out how fmx messaging mechanism works and if it meets the requirements, you can subclass your form's window by conventional means. Below example uses SetWindowSubclass.

...
protected
  {$IFDEF MSWINDOWS}
  procedure CreateHandle; override;
  procedure DestroyHandle; override;
  procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
  procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
  {$ENDIF}
...

implementation

{$IFDEF MSWINDOWS}
uses
  FMX.Platform.Win, Winapi.Commctrl;

function SubclassProc(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM;
  uIdSubclass: UINT_PTR; RefData: DWORD_PTR): LRESULT; stdcall;
var
  Self: TfMain;
  Message: TMessage;
begin
  Result := DefSubclassProc(Wnd, Msg, wParam, lParam);
  case Msg of
    WM_QUERYENDSESSION, WM_ENDSESSION:
    begin
      Self := TfMain(RefData);
      Message.Msg := Msg;
      Message.WParam := wParam;
      Message.LParam := lParam;
      Message.Result := Result;
      Self.Dispatch(Message);
      Result := Message.Result;
    end;
  end;
end;

procedure TfMain.CreateHandle;
var
  Wnd: HWND;
begin
  inherited;
  Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
  SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self));
end;

procedure TfMain.DestroyHandle;
var
  Wnd: HWND;
begin
  Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
  RemoveWindowSubclass(Wnd, SubclassProc, 1);
  inherited;
end;

procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  // do not call inherited here, there's no inherited handling
end;

procedure TfMain.WMEndSession(var Msg: TWMEndSession);
begin
  // do not call inherited here, there's no inherited handling
end;

var
  ICC: TInitCommonControlsEx;

initialization
  ICC.dwSize := SizeOf(ICC);
  ICC.dwICC := ICC_STANDARD_CLASSES;
  InitCommonControlsEx(ICC);
{$ENDIF}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Thank you very much. This is what I need! It works fine. – Gianluca Colombo Oct 03 '16 at 23:00
  • You can also take a look at the [docwiki](http://docwiki.embarcadero.com/RADStudio/Berlin/en/Using_the_RTL_Cross-Platform_Messaging_Solution) and on another [discussion](http://stackoverflow.com/questions/9186098/firemonkey-message-handling) – Danilo Casa Oct 06 '16 at 10:50
1

There have been a number of changes in this area in Windows in (relatively) recent releases - i.e. going back to Windows XP. In addition, the way that Delphi windows are managed by the Application have been changed to better behave with respect to other OS changes and things are different again in FMX.

WM_QUERYENDSESSION is now only sent to top-level windows. If your application is a VCL application and has MainFormOnTaskbar set TRUE then your application main form is a top-level window and should receive the message. If MainFormOnTaskbar is set FALSE, or if your form is not the main form (despite the name) then it is not a top-level window and will not receive the message.

If your application uses FMX then you will need to dig around inside the FMX.Platform.Win WindowService to determine exactly how the parenting of your main form is determined. Based on an inspection of [XE4] FMX source, things appear to have gone backwards in this area (relative to the VCL) and there are some ugly code smells here.

The problems that the finer details in this area cause are that from Vista onward, WM_QUERYENDSESSION is no longer sent to applications without any visible top-level windows. Even if your main form is a top-level window, if it is not visible at the point that Windows is shutting down then this could be why you are not receiving the message.

If the problem is that your window is not the top-level window in your application then there should be enough information here to enable you to at least figure out why.

In a VCL application, making your Main Form the taskbar window should address the problem. Whether there is a similar means to address the issue in an FMX application I don't know.

If you do have a valid top-level window and the problem is that your top-level window is (sometimes) not visible then you will need to find some other mechanism to hook into the shutdown process but should be aware that any behaviour that relies on other processes needs to take account of the fact that those other processes themselves amy be shutting down and may not be available.

Of course, all this is highly specific to Windows shutdown notifications. If you are intending to support other platforms with your FMX application then you will need to deal with shutdown behaviour differently there, assuming that FMX does not provide a cross-platform shutdown notification solution (otherwise you would be using that, no?).

(And if you are in fact only targeting Windows, why on Earth are you using FMX ?)

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • OnMessage won't help because this message is delivered synchronously – David Heffernan Oct 01 '16 at 21:03
  • I forgot to say that the project is not a VCL project but a multi device application FMX so I can't find MainFormOnTaskbar property on tApplication class – Gianluca Colombo Oct 01 '16 at 22:23
  • 1
    Good point - delivered direct to the windowproc. But given that we now know this is a multi-device project the whole question of Windows specific messaging problems in this area would appear to be a complete irrelevance ! Use the cross-platform shutdown notification process provided by FMX. if there isn't one then... well.... you have a whole different problem. – Deltics Oct 01 '16 at 23:01
  • Yes, bit of a bombshell that this is an FMX app. How that info could be omitted is beyond me. I'm not sure at all that message directive is wired up under FMX. – David Heffernan Oct 02 '16 at 07:38
  • I'm sorry, you are right, but what did you think when you read "{$IFDEF MSWINDOWS}" in my code?? A Vcl project?? OMG – Gianluca Colombo Oct 02 '16 at 09:25
  • 1
    Why are we having to guess? All it needed was a MCVE. – David Heffernan Oct 02 '16 at 11:02
  • For sure you don't have to guess and It was my mistake and for that I apologize. My disappointment was about your "How that info could be omitted" Yes!! Right!! but if I'm here to ask help is probably because this particular wasnt important to me. May be I'm a beginner! Not?? – Gianluca Colombo Oct 03 '16 at 12:34
0

The answer is probably belated, but those who will face the problem in the future, then ... The form has an OnSaveState event, it is just called on the WM_ENDSESSION event. Yes, it is handled by default. The WM_QUERYENDSESSION event is the same, but 1 is passed there by default (enable session termination). It can only be overridden by copying the FMX.Platform.Win.pas module and modifying it for yourself

HemulGM
  • 58
  • 1
  • 5