AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Generic Callback Example - EnumWindows
Goto page 1, 2, 3, 4  Next
 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
JGR



Joined: 15 Jun 2006
Posts: 52
Location: Unavailable until ~30th August

PostPosted: Sun May 20, 2007 3:15 pm    Post subject: Generic Callback Example - EnumWindows Reply with quote

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
View user's profile Send private message
BoBoĻ
Guest





PostPosted: Sun May 20, 2007 3:27 pm    Post subject: Reply with quote

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. Cool
Back to top
majkinetor



Joined: 24 May 2006
Posts: 3593
Location: Belgrade

PostPosted: Sun May 20, 2007 3:35 pm    Post subject: Reply with quote

Extraordinary work. Thx.
_________________
Back to top
View user's profile Send private message MSN Messenger
JGR



Joined: 15 Jun 2006
Posts: 52
Location: Unavailable until ~30th August

PostPosted: Sun May 20, 2007 3:37 pm    Post subject: Reply with quote

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 Smile

JGR
Back to top
View user's profile Send private message
corrupt



Joined: 29 Dec 2004
Posts: 2383

PostPosted: Sun May 20, 2007 5:08 pm    Post subject: Reply with quote

Thanks Smile
Back to top
View user's profile Send private message Visit poster's website
Sean



Joined: 12 Feb 2007
Posts: 1249

PostPosted: Sun May 20, 2007 6:07 pm    Post subject: Reply with quote

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
View user's profile Send private message
JGR



Joined: 15 Jun 2006
Posts: 52
Location: Unavailable until ~30th August

PostPosted: Sun May 20, 2007 6:25 pm    Post subject: Reply with quote

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
View user's profile Send private message
corrupt



Joined: 29 Dec 2004
Posts: 2383

PostPosted: Sun May 20, 2007 9:39 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
corrupt



Joined: 29 Dec 2004
Posts: 2383

PostPosted: Mon May 21, 2007 12:14 am    Post subject: Reply with quote

Thanks again JGR Smile . 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 Smile
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
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 2472
Location: Australia, Qld

PostPosted: Mon May 21, 2007 1:30 am    Post subject: Reply with quote

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
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1249

PostPosted: Mon May 21, 2007 4:02 am    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2472
Location: Australia, Qld

PostPosted: Mon May 21, 2007 5:04 am    Post subject: Reply with quote

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. Confused

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". Smile
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1249

PostPosted: Mon May 21, 2007 6:51 am    Post subject: Reply with quote

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
View user's profile Send private message
JGR



Joined: 15 Jun 2006
Posts: 52
Location: Unavailable until ~30th August

PostPosted: Mon May 21, 2007 6:51 am    Post subject: Reply with quote

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
View user's profile Send private message
corrupt



Joined: 29 Dec 2004
Posts: 2383

PostPosted: Mon May 21, 2007 1:57 pm    Post subject: Reply with quote

JGR wrote:
As for license, treat it as you would anything else on the forum. Use it as you see fit.
Thanks Smile . 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
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Goto page 1, 2, 3, 4  Next
Page 1 of 4

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group