Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Generic Callback Example - EnumWindows


  • Please log in to reply
57 replies to this topic
JGR
  • Members
  • 59 posts
  • Last active: Feb 07 2012 08:49 PM
  • Joined: 15 Jun 2006
The link archive contains a small DLL and a sample AHK script which calls EnumWindows, using a callback stub produced by the DLL.
Any number of callbacks stubs can be produced.
There is an overhead of 84+4*num of params bytes of heap memory allocated using GlobalAlloc (which can be manually freed after use using GlobalFree) per added callback, and one call to SendMessage to relay the message to AHK per call to that callback.

The callback handler assumes the STDCALL/Pascal calling convention (ie. not C's).

https://ahknet.autoh...GR/callback.rar

JGR

BoBo¨
  • Guests
  • Last active:
  • Joined: --
If this script (archive) contains something usefull for noobs too, would you/someone mind to explain (using a noobish example) what it's for ?

Btw. thanks for sharing it. 8)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Extraordinary work. Thx.
Posted Image

JGR
  • Members
  • 59 posts
  • Last active: Feb 07 2012 08:49 PM
  • Joined: 15 Jun 2006
The DLL makes it feasible to implement a callback routine in AHK.
A callback is a function we define which somebody else (ie. Windows, etc.) calls, instead of us calling somebody else. There are quite a lot of windows api functions which expect to be passed pointers to callback routines.

The noobish example is in the archive, really. You call windows, and it calls your function multiple times with the data you asked it for.

The DLL is used because AHK does not natively support pointers to functions.

This is not something that will be useful in all cases, only specific ones.

If you didn't get that, then you probably won't be needing to use it :)

JGR

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004
Thanks :)

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Superb! Now we have callback.
BTW, you seem to forget to set "DetectHiddenWindows On" in EnumWindowsPrc.
So, I added it.

DetectHiddenWindows, On
Process, Exist
hAHK := WinExist("ahk_pid " . ErrorLevel)

WM_AHK_Callback := DllCall("RegisterWindowMessage", "str", "WM_AHK_Callback")
OnMessage(WM_AHK_Callback, "EnumWindows")

hModule := DllCall("LoadLibrary", "str", "callback")
hProc := DllCall("callback\callbackit", "Uint", 2, "Uint", hAHK, "Uint", WM_AHK_Callback, "Uint", 0)
DllCall("EnumWindows", "Uint", hProc, "Uint", 0)
MsgBox, % Out

DllCall("FreeLibrary", "Uint", hModule)
ExitApp
	
EnumWindows(wParam, lParam)
{
	Global Out
	DetectHiddenWindows, On
	hWnd := DecodeInteger(lParam)
	WinGetTitle, sTitle, ahk_id %hWnd%
	WinGetClass, sClass, ahk_id %hWnd%
	Out .= "hWnd: ". hWnd . "`tTitle: " . sTitle . "`tClass: " . sClass . "`n"
	Return 1
}

DecodeInteger(ref, nSize = 4)
{
	DllCall("RtlMoveMemory", "int64P", val, "Uint", ref, "Uint", nSize)
	Return val
}

EncodeInteger(ref, val = 0, nSize = 4)
{
	DllCall("RtlMoveMemory", "Uint", ref, "int64P", val, "Uint", nSize)
}


JGR
  • Members
  • 59 posts
  • Last active: Feb 07 2012 08:49 PM
  • Joined: 15 Jun 2006
I've updated the linked archive to include that and the FreeLibrary call (and GlobalFree of the callback stub).
Thanks for spotting that.

I will say though that you really don't need to use RegisterWindowMessage, as the only window which will be receiving the message is the AHK one. RegisterWindowMessage realistically is only used by message broadcasters.
All you need to do is pick a number between 0x400 and 0x7FFF, which you haven't already used in your script (for other callbacks etc.), and use that.

JGR

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004

I will say though that you really don't need to use RegisterWindowMessage, as the only window which will be receiving the message is the AHK one.

One of the reasons for using it might be if you are creating a function to be included in other scripts. This way you can avoid collisions with other message handlers.

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004
Thanks again JGR :) . Btw, I didn't notice a license in the download. Is this available for commercial use, distribution, etc... ?

I decided to try and put together a small function to make #Including in generic scripts a bit easier. Here's a rough, initial beta version for fun :)
SetCallbackFunction("EnumWindows")
Sleep, 200
DllCall("EnumWindows","UInt",EnumWindows_PS,"UInt",0)
MsgBox Output1:`n%out%

out=
SetCallbackFunction("EnumWindows", "Off")
Sleep, 200
DllCall("EnumWindows","UInt",EnumWindows_PS,"UInt",0)
MsgBox Output2:`n%out%
Return




; *****************************************************************************
SetCallbackFunction(FunctionName, _Remove=0) {
Global
Static _CallbackDllLoaded := false
Local _pid, _hwnd, _hHookModule, _MsgNmb
If (_Remove) or (_Remove = "Off") {
  If %FunctionName% is integer
  {
    OnMessage(%FunctionName%, "")
    %FunctionName% := ""
    %FunctionName%_PS := ""
    Return "Removed Callback"
  }
  Else
    Return "ERROR: Invalid Message Number"
}
_pid:=DllCall("GetCurrentProcessId","Uint")
DetectHiddenWindows, On
_hwnd:=WinExist("ahk_pid " . _pid)

If !(_CallbackLoaded) {
  _hHookModule := DllCall("LoadLibrary", "str", "callback\callback.dll")
  _CallbackLoaded := true
  }
_MsgNmb := DllCall("RegisterWindowMessage", "Str", FunctionName) 
%FunctionName%_PS:=DllCall("callback\callback.dll\callbackit", "UInt", 2,"UInt", _hwnd, "UInt", _MsgNmb, "UInt", 0)
OnMessage(_MsgNmb, FunctionName)
%FunctionName% := _MsgNmb
Return _MsgNmb
}

; *****************************************************************************
EnumWindows(wParam, lParam, msg, hwnd) {
  global out
  DetectHiddenWindows, On 
  winid:=GetDeRefInteger(lParam)
  otherparam:=GetDeRefInteger(lParam+4)
  WinGetTitle, text, ahk_id %winid%
  WinGetClass, class, ahk_id %winid%
  out.="HWND: ". winid . "`tTitle: " . text . "`tClass: " . class . "`n"
  return 1
}

; *****************************************************************************
GetDeRefInteger(pSource, pIsSigned = false, pSize = 4)
; pSource is an integer pointer to a raw/binary integer
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
{
	Loop %pSize%  ; Build the integer by adding up its bytes.
		result += *(pSource + A_Index-1) << 8*(A_Index-1)
	if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
		return result  ; Signed vs. unsigned doesn't matter in these cases.
	; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
	return -(0xFFFFFFFF - result + 1)
}


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I don't get it... why decode lParam? wParam and lParam are already numbers in AHK (string) format...

hWnd := DecodeInteger(lParam)



Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I don't get it... why decode lParam? wParam and lParam are already numbers in AHK (string) format...


Notice that it's lParam, not &lParam.
The first argument of DecodeInteger() is a pointer.
Maybe my usage of the term ref as the variable name confused you.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Notice that it's lParam, not &lParam.
The first argument of DecodeInteger() is a pointer.
Maybe my usage of the term ref as the variable name confused you.

Ah, thanks, I get it now. I subconsciously assumed "DecodeInteger" would accept an integer, hence my confusion. Also, I didn't quite understand how DecodeInteger was working. Now I see you aren't actually decoding the integer - AutoHotkey is automatically converting it because the parameter is declared as "int64P." (Right?)

So it seems the name of the function comes from the usage of DllCall itself, not the dll function called. :?

Would DecodeInteger work for 32-bit (or less) signed integers? I see it accepts a "size" parameter, but the output is always intepreted as a 64-bit integer.


The "ref" variable didn't confuse me, though I would have understood at a glance had it been named "ptr". :)

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Would DecodeInteger work for 32-bit (or less) signed integers? I see it accepts a "size" parameter, but the output is always intepreted as a 64-bit integer.


That is at the heart of this function, as all types of integers are part of 64bit integer.

nSize = 1 -> decoded as unsigned  8bit integer
nSize = 2 -> decoded as unsigned 16bit integer
nSize = 4 -> decoded as unsigned 32bit integer
nSize = 8 -> decoded as "signed" 64bit integer
Similar with EncodeInteger().
BTW, there will be no problems encoding negative 8/16/32-bit integers.

JGR
  • Members
  • 59 posts
  • Last active: Feb 07 2012 08:49 PM
  • Joined: 15 Jun 2006
The value in lParam is a numeric pointer to the offset where the first parameter of the callback is in memory.
In C terms that is *int[], I believe.
As for how the DecodeInteger function works, it is more to do with the quirkiness of AHK on pointers than any fancy operation, but it does take an integer.
What you are really doing is:
hWnd = *lParam;

As for license, treat it as you would anything else on the forum. Use it as you see fit.
My advice however, is not to modify the DLL source code unless you really know what you are doing and understand the existing code (ie. you write assembly code).

JGR

Edit:
Re-uploaded archive, as DLL changed to reduce memory overhead per callback to 60 and alleviate the need to allocate a buffer to params and copy them (superfluous). lParam is now a stack pointer as in the hook dll.
This does not affect the AHK scripts at all.

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004

As for license, treat it as you would anything else on the forum. Use it as you see fit.

Thanks :) . If you get a chance, please have a look at the SetCallbackFunction function I posted above and consider using it in sample scripts (or something similar if you'd prefer a different method). The reason I'm mentioning this is that a Standard Library is planned and something like this would be much better if we could include it in a script easily. Although the code I posted is a bit rough, it only requires a user to enter one line of code to get callback fuctionality when using your dll. Here's a slightly modified version that will work if the dll is placed in an /Include subfolder in the AutoHotkey directory.
SetCallbackFunction(FunctionName, _Remove=0) {

Global

Static _CallbackDllLoaded := false

Local _pid, _hwnd, _hHookModule, _MsgNmb

If (_Remove) or (_Remove = "Off") {

  If %FunctionName% is integer

  {

    OnMessage(%FunctionName%, "")

    %FunctionName% := ""

    %FunctionName%_PS := ""

    Return "Removed Callback"

  }

  Else

    Return "ERROR: Invalid Message Number"

}

_pid:=DllCall("GetCurrentProcessId","Uint")

DetectHiddenWindows, On

_hwnd:=WinExist("ahk_pid " . _pid)

If !(_CallbackLoaded) {

   SplitPath, A_AhkPath,,x_LibPath 

   x_LibPath .= "\Include\callback.dll"

   Loop, %x_LibPath%

     x_LibPath = %A_LoopFileShortPath%

  _hHookModule := DllCall("LoadLibrary", "str", x_LibPath)

  _CallbackLoaded := true

  }

_MsgNmb := DllCall("RegisterWindowMessage", "Str", FunctionName) 

%FunctionName%_PS:=DllCall(x_LibPath . "\callbackit", "UInt", 2,"UInt", _hwnd, "UInt", _MsgNmb, "UInt", 0)

OnMessage(_MsgNmb, FunctionName)

%FunctionName% := _MsgNmb

Return _MsgNmb

}