Jump to content

Hook WH_CALLWNDPROC and capture all WM_ACTIVATEAPP


Recommended Posts

Warning: The code I have posted is relatively low-level system code.  As such, please do not attempt to run it unless you are sure you know what you are doing.  Furthermore, don't attempt to run it until after reading my entire post.

Simple version of my question: Can AutoIt be used to install system hooks of the WH_CALLWNDPROC type?

Preliminary (presently very unsure) answer: perhaps not...

Slightly more detail: I have been attempting to create a WH_CALLWNDPROC type hook procedure using _WinAPI_SetWindowsHookEx() but so far my efforts have failed.  If you know how to do this properly (or are certain AutoIt can't do it...) please advise.

A lot more detail: What I originally set out to do was create a script that always knows the handle of the window which was active prior to the script becoming active.  My initial solution was to use GUIRegisterMsg($WM_NCLBUTTONDOWN, 'titleBarClick') and then in my 'titleBarClick' function do the following:

1: Use _WinAPI_GetForegroundWindow() to get the desired handle.

2: Make my script active with _WinAPI_SetForegroundWindow()

Unfortunately, this only works correctly if my script is activated by clicking its 'Non-Client area' (the title bar).  One situation which is particularly slippery is if my script gets activated by the user clicking its taskbar button at the bottom of the screen.  (Refer to the following regarding the taskbar activation conundrum: http://stackoverflow.com/questions/885623/get-window-handle-of-last-activated-window)

So, based on the idea in the above Stack Overflow link I decided attempt a more generalized solution to getting the handle of the prior active window by hooking WH_CALLWNDPROC and keeping track of all WM_ACTIVATEAPP messages.  My starting point was the _WinAPI_SetWindowsHookEx() example code found at http://www.autoitscript.com/autoit3/docs/libfunctions/_WinAPI_SetWindowsHookEx.htm  After going through the example code I removed all the 'fluff' and reduced it down to a prototype of sorts:

#include <WinAPI.au3>

Global $hHook, $hStub_MyProc
Local $hmod

OnAutoItExitRegister("Cleanup")
$hStub_MyProc = DllCallbackRegister("_MyProc", "LRESULT", "int;wparam;lparam")
$hmod = _WinAPI_GetModuleHandle(0)
$hHook = _WinAPI_SetWindowsHookEx($WH_KEYBOARD_LL, DllCallbackGetPtr($hStub_MyProc), $hmod)

While 1
   Sleep(10)
WEnd

Func _MyProc($nCode, $wParam, $lParam)
   Return _WinAPI_CallNextHookEx($hHook, $nCode, $wParam, $lParam)
EndFunc

Func Cleanup()
   _WinAPI_UnhookWindowsHookEx($hHook)
   DllCallbackFree($hStub_MyProc)
EndFunc

In the above prototype code the hook procedure type is set to WH_KEYBOARD_LL, which seems to work fine.  However, after testing the above with all 15 of the hook procedure types / idHook values I have found it works for only 12 of the 15.  The following three procedure types give problems: WH_CALLWNDPROC, WH_CALLWNDPROCRET and WH_DEBUG.  Note that 'problems' is probably an understatement, on my system (XP SP3) swapping any of the three 'problematic' hook procedure types in to the above code totally crashes the OS (killing AutoIt with the Task Manager doesn't help -- a reboot is required).  In fact, the crash is about the most violent I have seen in XP ever (it even triggers Data Execution Prevention / DEP).  I'm not sure what would happen in Windows 7.  If you experiment with the above code please be extremely careful.

One observation: in the example code at http://www.autoitscript.com/autoit3/docs/libfunctions/_WinAPI_SetWindowsHookEx.htm the DllCallbackRegister() return type is set to "long" but it my code the return type is set to "LRESULT".  My decision to use "LRESULT" as the return type is based on the MSDN Library Windows API documentation at http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(SetWindowsHookEx);k(DevLang-C);k(TargetOS-WINDOWS)&rd=true  If anyone has comments on which return type is correct I'd be interested in that as well.

Link to comment
Share on other sites

Try this:

#include <GUIConstantsEx.au3>
#include <WinAPI.au3>

Opt( "MustDeclareVars", 1 )

Global Const $tagCWPSTRUCT = "lparam lParam;wparam wParam;uint message;hwnd hwnd"

Global $hHook

MainScript()


Func MainScript()

  Local $hGui = GUICreate( "Test", 300, 200, -1, 300 )

  Local $hStub_MyProc = DllCallbackRegister("_MyProc", "LRESULT", "int;wparam;lparam")
  $hHook = _WinAPI_SetWindowsHookEx($WH_CALLWNDPROC, DllCallbackGetPtr($hStub_MyProc), 0, _WinAPI_GetCurrentThreadId())

  GUISetState()

  While 1
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
    EndSwitch
  WEnd

  GUIDelete( $hGui )

  _WinAPI_UnhookWindowsHookEx($hHook)
  DllCallbackFree($hStub_MyProc)

  Exit

EndFunc


Func _MyProc($nCode, $wParam, $lParam)
  Local $tCWPSTRUCT = DllStructCreate( $tagCWPSTRUCT, $lParam )
  Local $hWnd = DllStructGetData( $tCWPSTRUCT, "hwnd" )
  ConsoleWrite( "$hWnd = " & $hWnd & @CRLF )
  Return _WinAPI_CallNextHookEx($hHook, $nCode, $wParam, $lParam)
EndFunc

To see some events in Scite console, move the "Test" window around with the mouse, minimize and restore the window, and switch forth and back between other windows.

Link to comment
Share on other sites

LarsJ,

After analyzing your code I am fairly sure it doesn't quite solve my problem.  Very neat example though!

I tried your code, which works fine on my system but as far as I can only registers events associated with itself.  What I'm really looking for is a way to detect every single WM_ACTIVATEAPP event no matter which running program it is associated with.  I'm guessing your code is limitied to monitoring itself because you set $dwThreadId to the return value of _WinAPI_GetCurrentThreadId().  I tried changing $dwThreadId to 0, since the AutoIt and MSDN documentation seem to indicate that would lead to all threads being monitored.  However, after making that change it appears the hook procedure _MyProc() is never called.  So I remain confused.  Also, why set $hmod to 0 rather than the return value of _WinAPI_GetModuleHandle(0)?

Link to comment
Share on other sites

This is just a simple example that shows how to use a $WH_CALLWNDPROC hook.

The main issue for catching global events is that the callback procedure must be implemented in a DLL. This is very clearly described in the MSDN documentation. And then you use $hMod as a handle for the DLL.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...