Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 781
Kaitnieks Avatar asked Apr 06 '11 14:04

Kaitnieks


2 Answers

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.

like image 58
Disillusioned Avatar answered Oct 05 '22 23:10

Disillusioned


After a little playing around. This has to be connected to Delphi's hidden main (real main) window you will need to look seriously at Application.initialise or Application.HookMainWindow().

Because amazingly this code does not cause a problem:

program w7logonsaver;
{$APPTYPE CONSOLE}

var
  i: Integer;
begin
  for i := 1 to 20 do
    writeln;
  write('K ');
  ReadLn;
end.

Just hit enter to quit.

like image 27
Despatcher Avatar answered Oct 06 '22 01:10

Despatcher