Generic Callback Example - EnumWindows
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
Btw. thanks for sharing it. 8)
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
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) }
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
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.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.
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) }
hWnd := DecodeInteger(lParam)
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.
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?)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.
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".
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 integerSimilar with EncodeInteger().
BTW, there will be no problems encoding negative 8/16/32-bit integers.
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.
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.As for license, treat it as you would anything else on the forum. Use it as you see fit.
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 }