解释 GetKeyState / GetCursorPos 的错误

8
有时候我会收到客户的错误报告,但是我却无法解释。在Delphi中执行Application.Run()之后,我会得到以下错误信息:
 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
-------------------------------------------------------------------

并且

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
-------------------------------------------------------------------

在这两种情况下,从Eurekalog提交的截图完全是黑色的。
有人能解释一下,是什么原因导致GetCursorPos或GetKeyState失败了吗?

3个回答

8

GetPhysicalCursorPos文档中提到:

当您调用GetPhysicalCursorPos时,输入桌面必须是当前桌面。请调用OpenInputDesktop以确定当前桌面是否为输入桌面。如果不是,请使用OpenInputDesktop返回的HDESK调用SetThreadDesktop以切换到该桌面。

您可能会在解锁工作站时遇到此问题。在我的代码中,我使用以下变量替换GetCursorPos:

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;

你可以使用你喜欢的代码挂钩机制来替换 GetCursorPos。我是这样做的:
RedirectProcedure(@Windows.GetCursorPos, @CodePatcher.GetCursorPos);

使用如下所述的RedirectProcedure在Delphi中修补例程调用

对于我的特定情况而言,GetCursorPos会失败,但是GetCursorInfo不会失败。但正如评论中指出的那样,GetCursorInfo也可能会失败。在这种情况下,您可能会发现安排修补程序不返回False是最方便的。

至于GetKeyState,我不确定。它很可能类似,但是GetKeyState是我个人不熟悉的API。


这很重要。你觉得忽略异常足够了吗? - Alois Heimer
@AloisHeimer 你会如何忽略异常?你怎么知道哪个异常需要被忽略?你又该如何实际忽略它呢? - David Heffernan
你如何知道该忽略哪一个?我希望通过 EOsError.ErrorCode 来帮我解决,同时使用调用堆栈来确定调用函数。你如何真正地忽略它?在全局异常处理程序中忽略它 - 我认为这可能会导致丢失一个键盘或鼠标事件。 - Alois Heimer
1
我的示例应用程序(在下面的另一个答案中提供)展示了GetCursorInfo在解锁工作站时与GetCursorPos一样会失败。我已经在Delphi 7中在Win7和Win10上确认了这一点。因此,您上面的答案也会导致引发异常。在我建议的地方添加Result := True可以避免在解锁工作站时引发异常。它是否会在其他地方创建其他问题,我不知道 :)。 - dougwoodrow
1
值得注意的是,在Delphi的新版本(至少在10.2中),TMouse.GetCursorPos不会引发异常,而只是将结果设置为(0,0)。 - PyScripter
显示剩余14条评论

1

参考:

  • 在Windows用户切换后登录
  • 从密码保护屏幕保护程序登录
  • VNC / 远程桌面服务

我将在全局异常处理程序中忽略这些错误,如下所示:

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;

注意:我收到了一个关于GetCursorPos的错误代码5(访问被拒绝)的报告。 - dougwoodrow
@dougwoodrow 谢谢!我也遇到了EndPaint的错误报告 - 我已经相应地修改了答案。 - Alois Heimer

0

奇怪的是,在我的测试中,我发现GetCursorInfoGetCursorPos在解锁密码锁定屏幕的情况下存在完全相同的问题。

(使用Delphi 7和Windows 7进行测试,并使用madshi.net的HookAPI进行修补)。

测试过程:

  1. 创建一个简单的应用程序,每秒钟调用一次Mouse.CursorPos(只需在主窗体上放置一个TTimer和TEdit,其中Timer代码如下所示)。
  2. 将CodePatcher单元添加到项目中。
  3. 运行应用程序。
  4. 锁定屏幕(Flag-L)。
  5. 输入密码以解锁屏幕。

使用David Heffernan的原始代码的结果:

//== 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 =================================================

当取消注释"Result := True;"行时的结果:

没有引发异常。


源文件

//==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========================================================================


移除计时器。这个问题与调用Mouse.CursorPos的计时器无关。 - David Heffernan
@david 如果移除计时器,即使不进行修补,错误也不会发生!这个例子的重点是要展示当工作站被锁定时,GetCursorInfo可能会像GetCursorPos一样失败。 - dougwoodrow
这是一个与我遇到的问题不同的问题,也很可能与提问者遇到的问题不同。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接