Search code examples
delphisetwindowshookex

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


Solution

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