8

Sometimes I get bug reports from customers, that I can't explain. After Application.Run() in Delphi I get the following errors:

 EOSError: System error: Code:_5 Access denied

 Call Stack Information:
-------------------------------------------------------------------
|Address |Module     |Unit       |Class |Procedure       |Line    |
-------------------------------------------------------------------
|Running Thread: ID=4352; Priorität=0; Klasse=; [Main Thread]     |
|-----------------------------------------------------------------|
|772B291F|USER32.dll |           |      |GetKeyState     |        |
|772B7B96|USER32.dll |           |      |GetPropA        |        |
|772B7B5A|USER32.dll |           |      |GetPropA        |        |
|772A7BC5|USER32.dll |           |      |DispatchMessageA|        |
|772A7BBB|USER32.dll |           |      |DispatchMessageA|        |
|00A6D804|Program.exe|Program.dpr|      |                |803[369]|  // Application.Run
-------------------------------------------------------------------

and

EOsError: A call to an OS function failed

Call Stack Information:
-------------------------------------------------------------------
|Address |Module     |Unit       |Class |Procedure       |Line    |
-------------------------------------------------------------------
|Running Thread: ID=2712; Priorität=0; Klasse=; [Main Thread]     |
|-----------------------------------------------------------------|
|7E379758|USER32.dll |           |      |GetCursorPos    |        |
|7E379ED9|USER32.dll |           |      |GetKeyState     |        |
|7E37B3FC|USER32.dll |           |      |CallNextHookEx  |        |
|7E380078|USER32.dll |           |      |GetPropA        |        |
|7E380042|USER32.dll |           |      |GetPropA        |        |
|7E3696C2|USER32.dll |           |      |DispatchMessageA|        |
|7E3696B8|USER32.dll |           |      |DispatchMessageA|        |
|00A6E823|Program.exe|Program.dpr|      |                |803[369]|  //Application.Run
-------------------------------------------------------------------

In both cases the screenshot submitted from Eurekalog is completely black.

Can anybody explain, what can cause GetCursorPos or GetKeyState to fail this way?

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
  • Similar to this, [`After COM API call, getting EOSError Exceptions (access denied) but only when switching users or Locking the station on Windows 7`](http://stackoverflow.com/q/12773935/576719). – LU RD Nov 22 '13 at 10:18

3 Answers3

8

The documentation for GetCursorPos says:

The input desktop must be the current desktop when you call GetCursorPos. Call OpenInputDesktop to determine whether the current desktop is the input desktop. If it is not, call SetThreadDesktop with the HDESK returned by OpenInputDesktop to switch to that desktop.

You can fall foul of this, most commonly when unlocking a workstation. In my code I replace GetCursorPos with this variant:

function GetCursorPos(var lpPoint: TPoint): BOOL; stdcall;
(* The GetCursorPos API in user32 fails if it is passed a memory address >2GB 
   which breaks LARGEADDRESSAWARE apps.  We counter this by calling GetCursorInfo
   instead which does not suffer from the same problem.

   In addition we have had problems with GetCursorPos failing when called 
   immediately after a password protected screensaver or a locked workstation 
   re-authenticates. This problem initially appeared with XP SP1 and is brought 
   about because TMouse.GetCursorPos checks the return value of the GetCursorPos 
   API call and raises an OS exception if the API has failed.
*)
var
  CursorInfo: TCursorInfo;
begin
  CursorInfo.cbSize := SizeOf(CursorInfo);
  Result := GetCursorInfo(CursorInfo);
  if Result then begin
    lpPoint := CursorInfo.ptScreenPos;
  end else begin
    lpPoint := Point(0, 0);
  end;
end;

You can use your favourite code hooking mechanism to replace GetCursorPos. I do it like so:

RedirectProcedure(@Windows.GetCursorPos, @CodePatcher.GetCursorPos);

with RedirectProcedure as described here: Patch routine call in delphi

It turned out for my particular scenario, that GetCursorPos would fail, but GetCursorInfo would not fail. But as has been pointed out in the comments, there are scenarios where GetCursorInfo will also fail. In that case you might find it expedient to arrange that the patched function does not return False.

As for GetKeyState, I'm not sure about that one. It's quite possibly similar but GetKeyState is an API that I am not personally familiar with.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • That is hefty. Do you think it would suffice to ignore the exception? – Alois Heimer Nov 22 '13 at 10:58
  • @AloisHeimer How would you go about ignoring the exception? How would you know which one to ignore? How would you actually ignore it? – David Heffernan Nov 22 '13 at 11:00
  • "How would you know which one to ignore?" I hope EOsError.ErrorCode can help me there + using call stack to determine the calling function. "How would you actually ignore it?" Ignore it in global exception handler - I think this would lead to one key / mouse event missed? – Alois Heimer Nov 22 '13 at 11:15
  • Global exception handler could do the trick. I'm sure my program used that approach once upon a time. I never liked it. But it would get the job done. – David Heffernan Nov 22 '13 at 11:17
  • I don't understand how this will prevent TMouse.GetCursorPos from causing an exception to be raised. Don't you need to return TRUE? if Result then begin lpPoint := CursorInfo.ptScreenPos; end else begin lpPoint := Point(0, 0); Result := True; end; – dougwoodrow Jan 29 '16 at 19:03
  • @david Okay, but if this new version does return False when called via TMouse.GetCursorPos then an exception will be raised, won't it? Under what circumstance does the GetCursorInfo call fail? – dougwoodrow Jan 29 '16 at 19:37
  • @doug The code can't just return True indiscriminately. – David Heffernan Jan 29 '16 at 20:12
  • In principle I agree, but in order to deal with the situations reported by the OP I think it is better to avoid confusing users with unnecessary exception reports. – dougwoodrow Jan 29 '16 at 20:21
  • @doug What confusing reports? – David Heffernan Jan 29 '16 at 20:24
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/102035/discussion-between-dougwoodrow-and-david-heffernan). – dougwoodrow Jan 29 '16 at 21:01
  • 1
    My example application (provided as another answer below) demonstrates that `GetCursorInfo` does in fact fail in the same way as `GetCursorPos` when unlocking a workstation. I've confirmed this in both Win7 and Win10, with Delphi 7. So your answer above will also result in an exception being raised. Adding the line `Result := True` where I suggested avoids the exception when unlocking a workstation. Whether it creates other problems elsewhere, I don't know :). – dougwoodrow Feb 03 '16 at 00:03
  • @doug Clearly your scenario is different from mine, and that in this question. Running a timer to call CursorPos seems like the obvious difference. – David Heffernan Feb 03 '16 at 07:30
  • How do you know that the OP's scenario was? There can be all sorts of reasons for a delayed call to the function. In my application the error occurred with an action menu. Can you provide a sample that illustrates the problem with `GetCursorPos` where `GetCursorInfo` doesn't fail? – dougwoodrow Feb 03 '16 at 09:04
  • @dougwoodrow The user accepted the answer which indicates that it solved their problem. Hence I can only assume that the askers scenario was addressed by this answer. The scenario that I encountered was a hint window. If the cursor happened to be over something that produced a hint, then returning from lock screen would produce `EOSError`. – David Heffernan Feb 03 '16 at 09:08
  • @david Actually Alois said your answer was accepted because it *explained* the problem. And he's avoiding the error via a global exception handler, which would prevent the error in my scenario too. So anyway, can you provide a sample app with a hint that illustrates the problem with `GetCursorPos`? – dougwoodrow Feb 03 '16 at 09:34
  • @dougwoodrow Why don't you ask a new question about your scenario? It's going to get lost here in the comments. I probably could concoct a sample app, but it would take time to do so. I don't think that's appropriate in a comment trail. – David Heffernan Feb 03 '16 at 09:36
  • @dougwoodrow Anyway, I updated my answer to make it clear that different scenarios call for different approaches. – David Heffernan Feb 03 '16 at 09:43
  • @david Thanks, I think that makes it a much better answer :). – dougwoodrow Feb 03 '16 at 09:50
  • 1
    Worth noting that in newer versions of Delphi (at least in10.2) TMouse.GetCursorPos does not raise an exception but just sets the result to (0,0) – PyScripter Oct 25 '18 at 22:53
1

For reference: Thanks to the answer and comments I found the following situations which might likely trigger these errors:

  • Login after user switching in Windows
  • Login from password protected screensavers
  • VNC / Remote Desktop Services

I will ignore the errors in a global excpetion handler as follows:

procedure MyGlobalExceptionHandler(Sender: TObject; E: Exception);
var
    TopCallStackFunction: string;
begin
    if E is EOSError then
    begin
        TopCallStackFunction := GetEurekalogTopCallStackFunction();

        //EOSError: System error: Code: 5 Access denied, 
        //caused by GetKeyState or EndPaint or GetCursorPos
        if ((E as EOSError).ErrorCode = Windows.ERROR_ACCESS_DENIED) 
          and ((TopCallStackFunction = 'GetKeyState') 
          or (TopCallStackFunction = 'EndPaint')
          or (TopCallStackFunction = 'GetCursorPos')) then
            Exit;

        //EOsError: A call to an OS function failed, caused by GetCursorPos 
        if ((E as EOSError).ErrorCode = 0) 
          and (TopCallStackFunction = 'GetCursorPos') then
            Exit;
    end;

    ... //other error handling
end;
Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
0

Strange, in my testing I find that GetCursorInfo exhibits exactly the same problem as GetCursorPos, at least in the situation where a password-locked screen is unlocked.

(Tested using Delphi 7 and Windows 7, and HookAPI from madshi.net for the patching).

Test procedure:

  1. Create a simple app simply calling Mouse.CursorPos every second (just a TTimer and a TEdit dropped on the main form, with the Timer code shown below).
  2. Add the CodePatcher unit to the project.
  3. Run the application.
  4. Lock the screen (Flag-L).
  5. Enter password to unlock the screen.

Outcome with David Heffernan's original code:

//== BOF BUG REPORT =================================================
operating system  : Windows 7 x64 Service Pack 1 build 7601
executable        : Project1.exe
compiled with     : Delphi 7
madExcept version : 3.0o

exception class   : EOSError
exception message : System Error. Code: 5. Access is denied.

main thread ($1b48):
0045c513 +73 Project1.exe SysUtils           RaiseLastOSError
0045c543 +07 Project1.exe SysUtils           Win32Check
00487dc1 +09 Project1.exe Controls 10578  +1 TMouse.GetCursorPos
0049dc7f +27 Project1.exe Unit1       32  +1 TForm1.Timer1Timer
//== EOF BUG REPORT =================================================

Outcome when the "Result := True;" line is uncommented:

No exception raised.


Source files

//==BOF PROJECT1.DPR========================================================================
program Project1;

uses
  madExcept,
  madLinkDisAsm,
  madListHardware,
  madListProcesses,
  madListModules,
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  CodePatcher in 'CodePatcher.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
//==EOF PROJECT1.DPR========================================================================

//==BOF UNIT1.PAS========================================================================
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Edit1: TEdit;
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Edit1.Text := IntToStr(Mouse.CursorPos.X);
end;

end.
//==EOF UNIT1.PAS========================================================================


//==BOF CODEPATCHER.PAS========================================================================
unit CodePatcher;

interface

implementation

uses
  Windows, Types, madCodeHook;

var GetCursorPosNextHook : function (var lpPoint: TPoint): BOOL; stdcall;

// David Heffernan's solution
function GetCursorPosHookProc(var lpPoint: TPoint): BOOL; stdcall;
var
  CursorInfo: TCursorInfo;
begin
  CursorInfo.cbSize := SizeOf(CursorInfo);
  Result := GetCursorInfo(CursorInfo);
  if Result then begin
    lpPoint := CursorInfo.ptScreenPos;
  end else begin
    lpPoint := Point(0, 0);
    // Uncomment next line to avoid exception caused by TMouse.GetCursorPos
    //Result := True;
  end;
end;

initialization
  HookAPI('user32.dll', 'GetCursorPos', @GetCursorPosHookProc, @GetCursorPosNextHook);

finalization
  UnhookAPI(@GetCursorPosNextHook);

end.
//==EOF CODEPATCHER.PAS========================================================================

dougwoodrow
  • 1,111
  • 12
  • 22
  • Remove the timer. The issue in this question does not involve a timer to invoke Mouse.CursorPos. – David Heffernan Feb 03 '16 at 07:32
  • @david If the timer is removed then the error won't occur even without patching! The point of this example is to show that `GetCursorInfo` can fail in the same way as `GetCursorPos` if it is called when the workstation is locked. – dougwoodrow Feb 03 '16 at 09:23
  • That's a different issue from the one that I encountered, and presumably different from the one that the asker encountered. – David Heffernan Feb 03 '16 at 09:34