Search code examples
delphiwindows-7screensaverauthentication

Windows 7 logon screensaver in Delphi


I'm having problems while using Delphi application as Windows 7 logon screensaver (for both 32-bit and 64-bit Windows). Even blank application (New Project without any extra code) throws an error.

Delphi 7 application throws "The memory could not be read" error and Delphi 2010 application throws "The exception unknown software exception occurred in the application" and then "Runtime error 217". This error happens before any form initialization and before any initialization of exception handlers.

Setting notepad.exe as logon screensaver works fine.

Any ideas what goes on here?


Solution

  • As I said in my comment, it's not "invisible code", just code in the initialization section of some unit that's causing the problem. I've managed to track down the culprit (well at least one of them - there may be others).

    When you use the Forms unit, it has a dependency on the Classes unit.

    The initialization section calls InitThreadSynchronization, which amongst other things calls the following:

    SyncEvent := CreateEvent(nil, True, False, '');
    if SyncEvent = 0 then
      RaiseLastOSError;
    

    It seems the API call CreateEvent fails when called from within the login screen. Unfortunately I'm unsure whether the login screen: (a) forbids CreateEvent altogether (b) requires CreateEventEx instead or (c) would work with an appropriate lpEventAttributes argument. I've posted a more specific question to hopefully find out: CreateEvent from Windows-7 Logon Screen

    You can verify the problem with the following console app:

    program TestLoginScreensaver;
    
    {$APPTYPE CONSOLE}
    
    uses
      Windows,
      SysUtils;
    
    var
      SyncEvent: THandle;
    
    begin
      try
        SyncEvent := CreateEvent(nil, True, False, '');
        if SyncEvent = 0 then
          RaiseLastOSError;
        CloseHandle(SyncEvent); //So handle is closed if it was created (e.g. while logged in)
      except
        on E:Exception do
          Writeln(E.Classname, ': ', E.Message);
      end;
      Readln;
    end.
    

    The purpose of SyncEvent is to enable TThread instances to synchronise back to the main thread. So if you write a single threaded app, or create your threads using something other than TThread, you don't actually need/use SyncEvent at all.

    SIDE-RANT: This is a prime example of the problem with using the initialization section. Merely including a unit has the potential to introduce unnecessary side-effects. They're Mostly Harmless, but not in this case. Now you may argue that Classes.pas is bloated, and I won't argue. But the point is that if Classes initialization were called explicitly from the DPR, this problem would have been easier to identify and find a workaround for.


    EDIT: New Solution

    As Remy Lebeau noted in the other question I posted.
    The line:

        SyncEvent := CreateEvent(nil, True, False, '');
    

    Must be changed to:

        SyncEvent := CreateEvent(nil, True, False, nil);
    

    Since this solution involves recompiling VCL units, you may want to go through a few of the previous questions on this subject

    With this as the only change (compiled in D2009) I was able to successfully show a blank form at the Logon screen. However, bear in mind that some things you may normally expect to be able to do will be off limits due to the security restrictions at the Logon screen.