Handling negative values in LParam DWORD in WM_MOVE (0x03) message Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Handling negative values in LParam DWORD in WM_MOVE (0x03) message

06 Feb 2017, 23:21

Alright, I haven't the slightest clue about bitshifting, properly converting between signed and unsigned types, and the likes.
This makes it damn near impossible to figure out where I even need to look for the problem here:

I am using the message WM_MOVE to monitor when a window is moved. It works really well and everything is fine... until I move the window to a negative X position.
instead of -100,600 I get 65436,600.
That's obviously because the negative wraps around to 2^16 + x.
Now, I could check of the number is closer to 0 or closer to 65536 and if it is the latter, subtract 65536, but obviously that would be an ugly hack (And what if you had a reeeally high desktop resolution? :P)

I was getting the values from the DWORD by simply using LoWord and HiWord functions:

Code: Select all

LoWord(byref dword)
{
	return, dword & 0xFFFF
}
HiWord(ByRef dword)
{
	return, dword >> 16
}
It looks like you shouldn't do that.
Microsoft docs even lay out the conversion: https://msdn.microsoft.com/en-us/librar ... s.85).aspx
C++

Code: Select all

xPos = (int)(short) LOWORD(lParam);   // horizontal position 
yPos = (int)(short) HIWORD(lParam);   // vertical position
And other sources point that out as well. Sometimes you'll see macros called GET_X_LPARAM and GET_Y_LPARAM referenced. That one seems to be exactly what we have above: http://www.cplusplus.com/forum/windows/143936/[/color]
The funny part about that: from the 'windowsx.h':
(Again, C++)

Code: Select all

#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
So, naturally, I tried to do that.
Google lead me to NumPut or NumGet for converting types in AHK.
As I type this, I have actually made it work, but I still would like some feedback (I can't imagine this is the best way to do it)

Here are my functions:

Code: Select all

LoWord(byref dword)
{
	return, dword & 0xFFFF
}
HiWord(ByRef dword)
{
	return, dword >> 16
}
UIntToShort(ByRef num)
{
	numPut(num,num,"UInt")
	return  numGet(num, "Short")
}

GET_X_LPARAM(lp)
{
	return, UIntToShort(LoWord(lp))
}
GET_Y_LPARAM(lp)
{
	return, UIntToShort(HiWord(lp))
}
It looks like I don't have to manually convert to Int or anything I guess... (the values are only 16 bits anyway, right?)
But do I really have to use both NumPut and NumGet like this?
Oh, btw: Script illustrating the process:
Creates a small window that you can drag around, showing you its location in GUI Text.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.

#SingleInstance force

GUI, new, +LastFound, Tester
GUI, -caption
GUI, font, s16 Bold
GUI, add, text, x30 y20 w200 h35 vXText, X:
GUI, add, text, x30 y70 w200 h35 vYText, Y:
GUI, show

OnMessage(0x201, "drag") ; WM_LBUTTONDOWN
OnMessage(0x3, "moving") ; WM_MOVE

drag(w,l,m,h)
{
	PostMessage, 0xA1, 2,,, % "Ahk_id " h ; Left Mouse Down on GUI sends Left Mouse Down on Non-Client Area (Like title bar), enabling you to drag the window even without title bar
}

moving(w, l, m, h)
{
	global
	GuiControl, Text, XText, % "X: " GET_X_LPARAM(l)
	GuiControl, Text, YText, % "Y: " GET_Y_LPARAM(l)
}

LoWord(byref dword)
{
	return, dword & 0xFFFF
}
HiWord(ByRef dword)
{
	return, dword >> 16
}
UIntToShort(ByRef num)
{
	numPut(num,num,"UInt")
	return  numGet(num, "Short")
}

GET_X_LPARAM(lp)
{
	return, UIntToShort(LoWord(lp))
}
GET_Y_LPARAM(lp)
{
	return, UIntToShort(HiWord(lp))
}
:morebeard:
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message  Topic is solved

07 Feb 2017, 00:33

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.

#SingleInstance force

GUI, new, , Tester
gui, font, s16 Bold
gui, add, text, x20 y20 w200 h30 vXText, X:
gui, add, text, x20 y70 w200 h30 vYText, Y:

OnMessage(0x201, "drag") ; WM_LBUTTONDOWN
OnMessage(0x03, "moving") ; WM_MOVE
gui, show
return

drag(w,l,m,h)
{
	PostMessage, 0xA1, 2,,, % "Ahk_id " h
}

moving(w, l, m, h)
{
	global
	GuiControl, Text, XText, % "X: "
        . (l & 0x8000 ? "-" ((~l)&0x7FFF)+1 : l&0x7FFF)
        . " [" GET_X_LPARAM(l) "]"
	GuiControl, Text, YText, % "Y: "
        . ((l>>16) & 0x8000 ? "-" ((~l>>16)&0x7FFF)+1 : (l>>16)&0x7FFF)
        . " [" GET_Y_LPARAM(l) "]"
}

LoWord(byref dword)
{
	return, dword & 0xFFFF
}
HiWord(ByRef dword)
{
	return, dword >> 16
}
UIntToShort(ByRef num)
{
	numPut(num,num,"UInt")
	return  numGet(num, "Short")
}

GET_X_LPARAM(lp)
{
	return, UIntToShort(LoWord(lp))
}
GET_Y_LPARAM(lp)
{
	return, UIntToShort(HiWord(lp))
}
Esc::ExitApp
- https://en.wikipedia.org/wiki/Two's_complement
- http://www.vbforums.com/showthread.php? ... -and-not-1 <-- I'm usually suggesting this link to explain why -1 is true in VB. Today I'm suggesting you read the second post which explains how to add a positive number to a negative number in binary.
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 08:55

kon wrote:

Code: Select all

...
        X := (l & 0x8000 ? "-" ((~l)&0x7FFF)+1 : l&0x7FFF)
...
        Y := ((l>>16) & 0x8000 ? "-" ((~l>>16)&0x7FFF)+1 : (l>>16)&0x7FFF)
- https://en.wikipedia.org/wiki/Two's_complement
- http://www.vbforums.com/showthread.php? ... -and-not-1 <-- I'm usually suggesting this link to explain why -1 is true in VB. Today I'm suggesting you read the second post which explains how to add a positive number to a negative number in binary.
(removed irrelevant code)

Hm, I remember Two's complement from https://www.youtube.com/watch?v=lKTsv6iVxV4...
But I'm still not sure what we're doing here exactly with each step o.o
It even looks like we're getting first the sign only, then the absolute value only, and tacking on a minus string ("-") if the number is negative...

Can you answer some of the questions I have?

Here is what I see looking at the X coord (LoWord):
  1. We have a shorthand à la condition ? outputIfTrue : outputIfNegative
  2. The condition uses bitwise AND for the input (which is a DWORD (32 bit)) and 0x8000 (which is 2^15; 1 + half the maximum value of a 16 bit integer in HEX)
  3. Somehow 2) determines if the number is negative. How?
  4. If the condition is true, we use bitwise AND for the bitwise inverted input and 0x7FFF (which is (2^15)-1; half the maximum value of a 16 bit integer in HEX), add one (to complete the negative representation using Two's complement), and get the absolute value of the negative LoWord, and return that with a prepended "-" string
  5. If not, we return the result of input AND 0x7FFF
Now I found num & 0xFFFF when searching for ways to get the LoWord out of a DWORD. I think it's just about starting to make sense to me:
Let HHHH LLLL be our DWORD, comprised of 8 HEX digits, where we're interested in getting the last 4 digits LLLL.
By using bitwise AND, all the leading digits (HHHH) will always be 0, since we're effectively saying 0xHHHHLLLL & 0x0000FFFF.
I'm still not entirely sure what bitwise AND does in Hex, but I can see X & 0 = 0 and X & F = X. Like a mask.Fine.
Getting the HiWord is even easier, you simply shift the lowest bits out of existence. Alright.

But what does num & 0x8000 do? or num & 0x7FFF?
Looking at a Lookup-Table (http://www.garykessler.net/library/byte ... table.html), I see that:
X & 7 returns 0 to 7 for inputs 0 to 7, but again 0 to 7 for inputs 8 to F.
Alright, that looks like it wraps around at 7 then. But then...:
X & 8 returns 0 for inputs 0 to 7, and 8 for inputs 8 to F. O.o

So maybe for even parity, X & Y returns 0 if X < Y, and Y if X >= Y?
And for odd parity, X & Y returns X mod Y?
Nope, think again, me...
What does bitwise and do in Hex? :D it's all too abstract for me

Now it looks like we're creating a threshold at half the capacity of a Short, to treat the value as a signed integer rather than an unsigned integer.
What does the AND operator do here?

I replaced the string business with a regular minus operator, and it still worked (of course I had to put the rest of the expression in parentheses (or turn "+1" into "-1") then.
Isn't that much much nicer? No parsing needed?
:morebeard:
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 09:23

jfi (found in my script collection)

Code: Select all

max(a, b)
{
    return (a > b) ? a : b
}

min(a, b)
{
    return (a < b) ? a : b
}

MAKEWORD(a, b)
{
    return (a & 0xff) | ((b & 0xff) << 8)
}

MAKELONG(a, b)
{
    return (a & 0xffff) | ((b & 0xffff) << 16)
}

LOWORD(l)
{
    return l & 0xffff
}

HIWORD(l)
{
    return (l >> 16) & 0xffff
}

LOBYTE(w)
{
    return w & 0xff
}

HIBYTE(w)
{
    return (w >> 8) & 0xff
}
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 09:50

jNizM wrote:jfi (found in my script collection)

Code: Select all

max(a, b)
{
    return (a > b) ? a : b
}

min(a, b)
{
    return (a < b) ? a : b
}

MAKEWORD(a, b)
{
    return (a & 0xff) | ((b & 0xff) << 8)
}

MAKELONG(a, b)
{
    return (a & 0xffff) | ((b & 0xffff) << 16)
}

LOWORD(l)
{
    return l & 0xffff
}

HIWORD(l)
{
    return (l >> 16) & 0xffff
}

LOBYTE(w)
{
    return w & 0xff
}

HIBYTE(w)
{
    return (w >> 8) & 0xff
}
Thanks. HIWORD() and LOWORD() are more or less the functions I'm already using to get the HiWord and LoWord (except I didn't null the leading digits of the HiWord because I expect the input to be just a 32 bit DWORD anyway)
But how does the rest help me in handling negative numbers expressed in what is effectively a UShort?
That is the question I'm after, and I can 't piece an answer together from your functions o.o

Of course, kon already provided a way which seems much more direct than using numPut() and numGet(), so the question is somewhat answered already.
But I'd like to know how it works, on a palpable level.
:morebeard:
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 09:55

The calculator that comes with windows, in "programmer" mode is a pretty good tool for visualizing this. Look at the binary representation of the numbers.
1. Yes. "Ternary" operator.
2. 0x8000 is 1000 0000 000 0000 in binary.
3. The leftmost bit is the "sign" bit. If it is 1 then the number is negative.
4. Yes
5. 0x00007FFF in binary: 0000 0000 0000 0000 0111 1111 1111 1111. If you AND this with any number, then it will remove all the bits from that number which do not match the binary 1's (effectively removing all but the lowest 15 bits).
I would suggest thinking about it more in binary than hex. Hex is a useful representation, but only once you can visualize the binary.
just me
Posts: 9450
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 10:01

Another option:

Code: Select all

; Numput() stores the value of lParam (DWORD / UInt) in x86 (litte endian / inverse) byte order.
; So 0xHHHLLHLL becomes LLLHHLHH in memory.
; The x-position (low word) is stored at address_of_buffer + 0.
; The y-position (high word) ist stored at address_of_buffer + 2.
; Using "Short" as type for NumGet() returnes the signed value.

GET_X_LPARAM(lParam) {
   NumPut(lParam, Buffer := "    ", "UInt")
   Return NumGet(Buffer, 0, "Short")
}
GET_Y_LPARAM(lParam) {
   NumPut(lParam, Buffer := "    ", "UInt")
   Return NumGet(Buffer, 2, "Short")
}
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 10:24

svArtist wrote:
  1. We have a shorthand à la condition ? outputIfTrue : outputIfNegative
  2. The condition uses bitwise AND for the input (which is a DWORD (32 bit)) and 0x8000 (which is 2^15; 1 + half the maximum value of a 16 bit integer in HEX)
  3. Somehow 2) determines if the number is negative. How?
Alright, I've basically said it already, I guess I can see how that one works.
As luck would have it, hex X & 8 returns 0 if x < 8 and 8 if X >= 8.
So the evaluation would be 0 when ever the number is below 0x8000. This is effectively true if X >= 8000 and false if X < 8000
It's just as luck would have it that our threshold is 0x8000, because if it was 0x7000 such an expression would not work (0x8000 & 0x7000 = false, for example, although it is obviously greater).

Since our address contains basically a signed Short, the leading bit determines the sign. So we have –32,768 to 32,767 stored in our 16 bits:
0 to 32,767 are 0x0000 to 0x7FFF; and -1 to –32,768 in 0x8000 to 0xFFFF; since the leading bit (sign bit) is used as 0 for positive and 1 for negative.
Am I correct?

Thus, everything over 0x7FFF will be a negative number.
Right?
:morebeard:
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 10:40

kon wrote:The calculator that comes with windows, in "programmer" mode is a pretty good tool for visualizing this. Look at the binary representation of the numbers.
1. Yes. "Ternary" operator.
2. 0x8000 is 1000 0000 000 0000 in binary.
3. The leftmost bit is the "sign" bit. If it is 1 then the number is negative.
4. Yes
5. 0x00007FFF in binary: 0000 0000 0000 0000 0111 1111 1111 1111. If you AND this with any number, then it will remove all the bits from that number which do not match the binary 1's (effectively removing all but the lowest 15 bits).
I would suggest thinking about it more in binary than hex. Hex is a useful representation, but only once you can visualize the binary.
:D Yes, I think I slowly got there just looking at the code some more.
You are so right that looking at the hex can be a distraction when you're not thinking about the binary first ^^

Now, looking at the binary notation, it becomes obvious what the significance of ANDing with 0x7FFF is :)
It just gives you the input number without the sign bit :D (provided input is 16 bit; more leading digits are nulled by ANDing with 0, as you said. Useful here because we're dealing with the LoWord)

...and the HiWord is simply shifted >>16 to remove the trailing LoWord, giving you just another 16 bit integer. Then you can do the same thing with that number.

I think I got it all, now!
(At first, it looked somewhat unelegant for the string "-" - but of course you can wrap the whole expression in a -() expression, and you get a regular, good old actual negative integer :))

Thanks so much, everybody!
This was important for me (and I hope for some others at some point, too); I hate copy/pasting things I don't understand myself!
:morebeard:
User avatar
svArtist
Posts: 62
Joined: 08 Mar 2015, 18:16

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

07 Feb 2017, 10:57

just me wrote:Another option:

Code: Select all

; Numput() stores the value of lParam (DWORD / UInt) in x86 (litte endian / inverse) byte order.
; So 0xHHHLLHLL becomes LLLHHLHH in memory.
; The x-position (low word) is stored at address_of_buffer + 0.
; The y-position (high word) ist stored at address_of_buffer + 2.
; Using "Short" as type for NumGet() returnes the signed value.

GET_X_LPARAM(lParam) {
   NumPut(lParam, Buffer := "    ", "UInt")
   Return NumGet(Buffer, 0, "Short")
}
GET_Y_LPARAM(lParam) {
   NumPut(lParam, Buffer := "    ", "UInt")
   Return NumGet(Buffer, 2, "Short")
}
Thanks!
I guess that makes sense. When I just trialed and errored my way to my above approach, I hadn't thought about how the memory was allocated. Using the offset to get to the HiWord is obviously the right idea :)
Otherwise, I seem to have found the same approach for converting from UInt to Short *yay* :)

Again, thanks to everybody who weighed in :)
:morebeard:
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Handling negative values in LParam DWORD in WM_MOVE (0x03) message

27 Oct 2018, 18:42

Thank you for this PRECIOUS tutorial on the everso complicated lParam! Found nowhere else than this post so thanks alot guys!!
I still don't understand how this all works but atleast I'm now able to use the lParam inside of WM_LBUTTONDOWN

it works with either of these two examples you guys provided:

Code: Select all

;lPx := (lParam & 0x8000 ? "-"((~lParam)&0x7FFF)+1 : lParam&0x7FFF)
;lPy := ((lParam>>16) & 0x8000 ? "-" ((~lParam>>16)&0x7FFF)+1 : (lParam>>16)&0x7FFF)
lPx:=GET_X_LPARAM(lParam), lPy:=GET_Y_LPARAM(lParam)

; Numput() stores the value of lParam (DWORD / UInt) in x86 (litte endian / inverse) byte order.
; So 0xHHHLLHLL becomes LLLHHLHH in memory.
; The x-position (low word) is stored at address_of_buffer + 0.
; The y-position (high word) is stored at address_of_buffer + 2.
; Using "Short" as type for NumGet() returns the signed value.
GET_X_LPARAM(lParam) {
	NumPut(lParam, Buffer := "    ", "UInt")
	Return NumGet(Buffer, 0, "Short")
}
GET_Y_LPARAM(lParam) {
	NumPut(lParam, Buffer := "    ", "UInt")
	Return NumGet(Buffer, 2, "Short")
}
:bravo: :beer:

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Anput, mcd, mikeyww, Nerafius and 136 guests