akira789
akira789

Reputation: 13

"E2036 Variable required" when trying to use SetWindowsHookEx

I am trying to duplicate the code in Intercepting Keyboard Input With Delphi. Following is code:

...
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    KBHook: HHook; {this intercepts keyboard input}
    {callback's declaration}
    function KeyboardHookProc(Code: Integer; WordParam: Word;
                              LongParam: LongInt): LongInt; stdcall;
  public
    { Public declarations }
  end;
...
...
procedure TForm1.FormCreate(Sender: TObject);
begin
  {Set the keyboard hook so we  can intercept keyboard input}
  KBHook := SetWindowsHookEx( WH_KEYBOARD,
                 {callback >} @KeyboardHookProc,
                              HInstance,
                              GetCurrentThreadId() );

end;


procedure TForm1.FormDestroy(Sender: TObject);
begin
   {unhook the keyboard interception}
   UnHookWindowsHookEx(KBHook) ;
end;


function TForm1.KeyboardHookProc(Code: Integer; WordParam: Word;
  LongParam: LongInt): LongInt;
begin
  ListBox1.Items.Add( 'Code: ' + Code.ToString);
  ListBox1.Items.Add( '  -- WordParam: ' + WordParam.ToString);
  ListBox1.Items.Add( '  -- LongParam: ' + LongParam.ToString);
  ListBox1.Items.Add( '' );

  Result := 0;
  {To prevent Windows from passing the keystrokes
   to the target window, the Result value must  be a nonzero value.}
end;
...

The code does not compile. The error:

[dcc32 Error] Unit1.pas(43): E2036 Variable required

The error points to the @KeyboardHookProc, the second argument to SetWindowsHookEx function.

I then tried the WH_SHELL and WH_GETMESSAGE with their own callback procedures, also without success, with the same error.

What did I miss?

Upvotes: 1

Views: 142

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595320

You cannot use a non-static class method as a hook callback (well, not without thunking it, anyway). It has a hidden Self parameter that the API has no knowledge of.

To remove that Self parameter, you need to declare the method as class ... static, eg:

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    KBHook: HHook; {this intercepts keyboard input}
    {callback's declaration}
    class function KeyboardHookProc(Code: Integer; WordParam: WPARAM;
                              LongParam: LPARAM): LRESULT; stdcall; static;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
  {Set the keyboard hook so we  can intercept keyboard input}
  KBHook := SetWindowsHookEx( WH_KEYBOARD,
                 {callback >} @KeyboardHookProc,
                              HInstance,
                              GetCurrentThreadId() );

end;


procedure TForm1.FormDestroy(Sender: TObject);
begin
   {unhook the keyboard interception}
   UnHookWindowsHookEx(KBHook) ;
end;

class function TForm1.KeyboardHookProc(Code: Integer; WordParam: WPARAM;
  LongParam: LPARAM): LRESULT;
begin
  Form1.ListBox1.Items.Add( 'Code: ' + Code.ToString);
  Form1.ListBox1.Items.Add( '  -- WordParam: ' + WordParam.ToString);
  Form1.ListBox1.Items.Add( '  -- LongParam: ' + LongParam.ToString);
  Form1.ListBox1.Items.Add( '' );

  Result := 0;
  {To prevent Windows from passing the keystrokes
   to the target window, the Result value must  be a nonzero value.}
end;

Alternatively, you can use a free-standing function (as the article you linked to is doing) instead of using a class method, eg:

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    KBHook: HHook; {this intercepts keyboard input}
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

...

{callback's declaration}
function KeyboardHookProc(Code: Integer; WordParam: WPARAM;
                          LongParam: LPARAM): LRESULT; stdcall;
begin
  Form1.ListBox1.Items.Add( 'Code: ' + Code.ToString);
  Form1.ListBox1.Items.Add( '  -- WordParam: ' + WordParam.ToString);
  Form1.ListBox1.Items.Add( '  -- LongParam: ' + LongParam.ToString);
  Form1.ListBox1.Items.Add( '' );

  Result := 0;
  {To prevent Windows from passing the keystrokes
   to the target window, the Result value must  be a nonzero value.}
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  {Set the keyboard hook so we  can intercept keyboard input}
  KBHook := SetWindowsHookEx( WH_KEYBOARD,
                 {callback >} @KeyboardHookProc,
                              HInstance,
                              GetCurrentThreadId() );

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
   {unhook the keyboard interception}
   UnHookWindowsHookEx(KBHook) ;
end;

Upvotes: 6

Related Questions