Recent

Author Topic: [SOLVED] Windows LCL apps crash on session switch  (Read 5855 times)

CCRDude

  • Hero Member
  • *****
  • Posts: 614
[SOLVED] Windows LCL apps crash on session switch
« on: December 07, 2015, 09:28:40 am »
When I've got a LCL application open (including Lazarus itself), and switch the desktop session (by connecting via Remote Desktop - doing a "Fast User Switch" would be the same), the app crashes:

Quote
Project X raised exception class 'Exernal: SIGSEGV'.
In file '.\include\monitor.inc' at line 21

This could be related to the multi monitor handling - the remote machine has a different monitor layout. Which would mean Fast User Switching woulddn't trigger this, but dynamically adding or removing screens would.

Steps to reproduce are simple: one Windows machine, Lazarus or LCL-based app open. Connect to RDP, Lazarus or app crashes.

One workaround would be to overwrite SetBounds of the main form and call Screen.Upda]"]>Blockednitors there (which causes the monitor list to be recreated new, having correct monitor handles).

A better workaround would be to hook WM_WTSSESSION_CHANGE on Windows and call Screen.Upda]"]>Blockednitors there (do not forget WTSRegisterSessionNotification... will test that workaround next).

Even better would be to hook generic screen connects/disconnects.

(PS: this is the same as this, except that the other post is speaking about the IDE, and I thought the problem might be better located here, since it's a LCL problem).
« Last Edit: July 06, 2017, 10:56:15 am by CCRDude »

balazsszekely

  • Guest
Re: Windows LCL apps crash on session switch
« Reply #1 on: December 07, 2015, 10:22:08 am »
No crash here(see attachment). However feel free to add a new issue to the bugtracker: http://bugs.freepascal.org/main_page.php

CCRDude

  • Hero Member
  • *****
  • Posts: 614
Re: Windows LCL apps crash on session switch
« Reply #2 on: December 15, 2015, 10:31:42 am »
Found the "culprit" in my application, and it probably is related to the issue in the Lazarus IDE itself as well. In FormChangeBounds, I was using Screen.MonitorFromWindow(Self.Handle) to get the dimensions of the monitor the monitor the form is displayed upon.


Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormChangeBounds(Sender: TObject);
  2. var
  3.    m: TMonitor;
  4. begin
  5.    try
  6.       // Screen.Upda]"]>Blockednitors;
  7.       // Screen.UpdateScreen;
  8.       m := Screen.MonitorFromWindow(Self.Handle);
  9.        Self.Caption:= Format('FormChangeBounds: %d x %d', [m.Width, m.Height]);
  10.    except
  11.      on E: Exception do begin
  12.        Self.Caption:= 'FormChangeBounds: ' + E.Message;
  13.      end;
  14.    end;
  15. end;                    
  16.  

Uncommenting the two lines avoids the crash.

I couldn't get the WM_WTSSESSION_CHANGE workaround to work yet, which would avoid unnecessary updates to the object on simple resizing the window though.

My bug tracker login from 2012 doesn't work (was stored in two different password managers identical), my community login neither (but then, trying to access http://community.freepascal.org:10000/ fails all the time), I tried six different email addresses, but couldn't find one my password could've been sent to. When I find some more free time, I might start guessing other email addresses I could've used.

Thaddy

  • Hero Member
  • *****
  • Posts: 17099
  • Ceterum censeo Trump esse delendam
Re: Windows LCL apps crash on session switch
« Reply #3 on: December 15, 2015, 11:17:45 am »
But that avoids  YOUR crash. It would fuck up MY multimonitor setup when in between one of the moniors is pulled. That is not a proper fix.
Probably there should be some code to check remote connections.

I interpreted the uncommenting in reverse, though. I assume you meant commenting out these two lines?
« Last Edit: December 15, 2015, 11:19:33 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

balazsszekely

  • Guest
Re: Windows LCL apps crash on session switch
« Reply #4 on: December 15, 2015, 11:36:35 am »
Quote
@CCRDude
I couldn't get the WM_WTSSESSION_CHANGE workaround to work yet, which would avoid unnecessary updates to the object on simple resizing the window though.
Here you go:
Code: Pascal  [Select][+][-]
  1. uses windows, messages, dynlibs;
  2.  
  3. var
  4.   PrevWndProc: WNDPROC;
  5.   SessionNotification: Boolean;
  6.  
  7. function WndCallback(AHWND: HWND; uMsg: UINT; wParam: WParam; lParam: LParam): LRESULT; stdcall;
  8. const
  9.   WTS_CONSOLE_CONNECT = 1;
  10.   WTS_CONSOLE_DISCONNECT = 2;
  11.   WTS_REMOTE_CONNECT = 3;
  12.   WTS_REMOTE_DISCONNECT = 4;
  13.   WTS_SESSION_LOGON = 5;
  14.   WTS_SESSION_LOGOFF = 6;
  15.   WTS_SESSION_LOCK = 7;
  16.   WTS_SESSION_UNLOCK = 8;
  17.   WTS_SESSION_REMOTE_CONTROL = 9;
  18. var
  19.   M: TMonitor;
  20. begin
  21.   if uMsg = WM_WTSSESSION_CHANGE then
  22.   begin
  23.     case wParam of
  24.       WTS_REMOTE_CONNECT:
  25.       begin
  26.         m := Screen.MonitorFromWindow(Form1.Handle);
  27.         Form1.Caption:= 'Remote logon ' +  Format('FormChangeBounds: %d x %d', [m.Width, m.Height]);
  28.         //make sure you don't block the hook chain with a blocking function like showmessage
  29.       end;    
  30.     end;
  31.   end;
  32.   Result := CallWindowProc(PrevWndProc, Ahwnd, uMsg, WParam, LParam);
  33. end;
  34.  
  35. function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
  36. type
  37.   TWTSRegisterSessionNotification = function(Wnd: HWND; dwFlags: DWORD): BOOL; stdcall;
  38. var
  39.   DllHandle: THandle;
  40.   WTSRegisterSessionNotification: TWTSRegisterSessionNotification;
  41.   FarProc: TFarProc;
  42. begin
  43.   Result := False;
  44.   DllHandle := LoadLibrary('Wtsapi32.dll');
  45.   if DllHandle > 0 then
  46.   begin
  47.     FarProc := GetProcAddress(DllHandle, 'WTSRegisterSessionNotification');
  48.     if FarProc <> nil then
  49.     begin
  50.       WTSRegisterSessionNotification := TWTSRegisterSessionNotification(FarProc);
  51.       Result := WTSRegisterSessionNotification(Wnd, dwFlags);
  52.     end;
  53.     FreeLibrary(DllHandle);
  54.   end;
  55. end;
  56.  
  57. function UnRegisterSessionNotification(Wnd: HWND): Boolean;
  58. type
  59.   TWTSUnRegisterSessionNotification = function(Wnd: HWND): BOOL; stdcall;
  60. var
  61.   DllHandle: THandle;
  62.   WTSUnRegisterSessionNotification: TWTSUnRegisterSessionNotification;
  63.   FarProc: TFarProc;
  64. begin
  65.   Result := False;
  66.   DllHandle := LoadLibrary('Wtsapi32.dll');
  67.   if DllHandle > 0 then
  68.   begin
  69.     FarProc := GetProcAddress(DllHandle, 'WTSUnRegisterSessionNotification');
  70.     if Farproc <> nil then
  71.     begin
  72.       WTSUnRegisterSessionNotification := TWTSUnRegisterSessionNotification(FarProc);
  73.       Result:= WTSUnRegisterSessionNotification(Wnd);
  74.     end;
  75.     FreeLibrary(DllHandle);
  76.   end;
  77. end;
  78.  
  79. procedure TForm1.FormCreate(Sender: TObject);
  80. begin
  81.   PrevWndProc := Windows.WNDPROC(SetWindowLongPtr(Self.Handle, GWL_WNDPROC, PtrInt(@WndCallback)));
  82.   SessionNotification := RegisterSessionNotification(Handle, 1);
  83.   if not SessionNotification then
  84.     ShowMessage('Cannot register session notification');
  85. end;
  86.  
  87. procedure TForm1.FormDestroy(Sender: TObject);
  88. begin
  89.   if SessionNotification then
  90.      UnRegisterSessionNotification(Handle);
  91. end;

CCRDude

  • Hero Member
  • *****
  • Posts: 614
Re: Windows LCL apps crash on session switch
« Reply #5 on: December 15, 2015, 12:25:45 pm »
@Thaddy: as I wrote, it's a first workaround, nothing I would recommend as a fix.

And I meant it the way I wrote it :) These two lines need to get called. Otherwise Screen.MonitorFromWindow would access invalid monitor handles, causing an app crash.

If your multimonitor setup relies on temporarily having monitors with invalid handles in this object, I'ld take that as a challenge to improve my design.

Thanks for hinting me at the option of temporarily disconnected monitors, will check if that would cause the same issues as well.

@GetMem: thanks for the code example! Contrary to my old code I ported from Delphi, this works :) I wrapped it up in a separate unit with just two calls to register and unregister the main form.

Instead of the new window procedure, a message handler could be also added to the form instead, which I'll do once adding it to the custom form class all my forms inherit from:
Code: [Select]
procedure DoSesssionChange(var Message: TMessage); message WM_WTSSESSION_CHANGE;

Btw, you mixed use and fix, in the handler, Screen.Upda]"]>Blockednitors and Screen.UpdateScreen need to be called.

Again, many thanks :)

I'll see if I can convert this into a fix for the IDE as well, and report back (plus report on the bugtracker)!

 

TinyPortal © 2005-2018