 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
JGR
Joined: 15 Jun 2006 Posts: 52 Location: Unavailable until ~30th August
|
Posted: Sun May 20, 2007 3:15 pm Post subject: Generic Callback Example - EnumWindows |
|
|
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).
http://www.autohotkey.net/~JGR/callback.rar
JGR |
|
| Back to top |
|
 |
BoBoĻ Guest
|
Posted: Sun May 20, 2007 3:27 pm Post subject: |
|
|
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.  |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3593 Location: Belgrade
|
Posted: Sun May 20, 2007 3:35 pm Post subject: |
|
|
Extraordinary work. Thx. _________________
 |
|
| Back to top |
|
 |
JGR
Joined: 15 Jun 2006 Posts: 52 Location: Unavailable until ~30th August
|
Posted: Sun May 20, 2007 3:37 pm Post subject: |
|
|
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 |
|
| Back to top |
|
 |
corrupt
Joined: 29 Dec 2004 Posts: 2383
|
Posted: Sun May 20, 2007 5:08 pm Post subject: |
|
|
Thanks  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 1249
|
Posted: Sun May 20, 2007 6:07 pm Post subject: |
|
|
Superb! Now we have callback.
BTW, you seem to forget to set "DetectHiddenWindows On" in EnumWindowsPrc.
So, I added it.
| Code: | 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)
}
|
|
|
| Back to top |
|
 |
JGR
Joined: 15 Jun 2006 Posts: 52 Location: Unavailable until ~30th August
|
Posted: Sun May 20, 2007 6:25 pm Post subject: |
|
|
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 |
|
| Back to top |
|
 |
corrupt
Joined: 29 Dec 2004 Posts: 2383
|
Posted: Sun May 20, 2007 9:39 pm Post subject: |
|
|
| JGR wrote: | | 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. |
|
| Back to top |
|
 |
corrupt
Joined: 29 Dec 2004 Posts: 2383
|
Posted: Mon May 21, 2007 12:14 am Post subject: |
|
|
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 | Code: | 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)
} |
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2472 Location: Australia, Qld
|
Posted: Mon May 21, 2007 1:30 am Post subject: |
|
|
I don't get it... why decode lParam? wParam and lParam are already numbers in AHK (string) format...
| Quote: | | Code: | | hWnd := DecodeInteger(lParam) |
|
|
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 1249
|
Posted: Mon May 21, 2007 4:02 am Post subject: |
|
|
| lexikos wrote: | | 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. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2472 Location: Australia, Qld
|
Posted: Mon May 21, 2007 5:04 am Post subject: |
|
|
| Sean wrote: | 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".  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 1249
|
Posted: Mon May 21, 2007 6:51 am Post subject: |
|
|
| lexikos wrote: | | 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.
| Code: | 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. |
|
| Back to top |
|
 |
JGR
Joined: 15 Jun 2006 Posts: 52 Location: Unavailable until ~30th August
|
Posted: Mon May 21, 2007 6:51 am Post subject: |
|
|
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. |
|
| Back to top |
|
 |
corrupt
Joined: 29 Dec 2004 Posts: 2383
|
Posted: Mon May 21, 2007 1:57 pm Post subject: |
|
|
| JGR wrote: | | 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. | Code: | 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
} |
|
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|