NumGet、NumPut 是 AHK 中比较高级的函数,但帮助中说明寥寥,例子也很少,实际使用起来就很容易因为不够清晰的细节,造成困惑。
本文将用数个例子,解释一些细节问题,帮助大家更好的使用这两个函数。
1.为什么 NumGet 取到的值与变量里的值不一致?
下面这个例子,可能会让你感到困惑。
明明给 test 赋值 45678 ,打印出来也是 45678 ,为什么使用 NumGet 获取后再打印就变 3478460 了呢?
; 请使用 U32 或 U64 版本运行本例
test := 45678
MsgBox, % test
MsgBox, % NumGet(test, "Int")
下面这个例子,将解释原因。
; 请使用 U32 或 U64 版本运行本例
; 因为 ahk 的变量没有类型,所以大多数情况下,变量的值都是以字符串形式存储的
; 因此 test 里存入的不是数值 45678 ,而是字符串 "45678"
; 又因为 unicode 版的 ahk 在内部以 UTF-16 格式存储字符串
; 所以变量 test 的内容应该是 34 00 35 00 36 00 37 00 38 00 (16进制的 34 00 代表字符 4)
; 最后,因为 windows 使用小端字节序,所以内存里的顺序其实是反的
; 因此变量 test 在内存里的实际内容是 00 38 00 37 00 36 00 35 00 34
test := 45678
; 打印显示为 45678
MsgBox, % test
; 什么是高低位? 45678 念作四万五千六百七十八,万是最高位,所以左边是高位,右边是低位
; Int 类型占4个字节,所以从 00 38 00 37 00 36 00 35 00 34 的低位开始取前4个字节 00 35 00 34
; 00 35 00 34(16进制) = 3473460(10进制)
; 所以打印显示为 3473460
MsgBox, % NumGet(test, "Int")
2.指针的解析
下面这个例子,将演示如何从指针中获取目标值。
; 将数值 45678 存入变量 var
VarSetCapacity(var, 4, 0)
NumPut(45678, var, "Int")
; 变量 pointer 存变量 var 的地址
; 即变量 pointer 是指向变量 var 的指针
pointer := &var
; 直接通过 pointer 获取 var 的值
; pointer+0 即强制将 var 的地址转为数字传入 NumGet 中
; 此时 NumGet 收到的第一个参数就是纯数字,所以它将被解析为地址
; 而这个地址就是变量 var 的地址,因此就能直接获取到 var 的值
; 也就是说,在这里 pointer+0 = var = &var
MsgBox, % NumGet(pointer+0, "Int")
; 直接通过 pointer 修改 var 的值
NumPut(12345, pointer+0, "Int")
MsgBox, % NumGet(pointer+0, "Int")
; 注意,不能使用中间变量赋值后再传入
; 因为中间变量赋值后,又会被自动转为字符串
; 所以下面的代码并不能得到预期的 12345
temp := pointer+0
MsgBox, % NumGet(temp, "Int")
3.NumGet、NumPut 均不支持*类型
帮助中说, NumGet、NumPut 关于类型的更多细节可以参考 DllCall 的类型说明。
但与 DllCall 不同的是, NumGet、NumPut 的类型并不支持*。
; 将数值 12345 存入变量 var
VarSetCapacity(var, 4, 0)
NumPut(12345, var, "Int")
; pointer 是指向变量 var 的指针
pointer := &var
; 如果支持*,那么此处应该取到值 12345
MsgBox, % NumGet(pointer, "Int*")
; 可以看到, Int* 取到的值并不是 12345
; 且 Int* 与 Int 甚至 Intabcdefg 取到的都是相同的值
; 因此说明,类型并不支持*
MsgBox, % NumGet(pointer, "Int")
MsgBox, % NumGet(pointer, "Intabcdefg")
; 可以看到, *Int 与 Int64 甚至 abcdefg 取到的都是相同的值
; 推测解析策略为,类型的开头部分如果匹配,则进行匹配,否则为 UPtr
; 当类型为 UPtr 时,因为 AHK 内部数字范围是 Int64 ,因此 U64 版本实际为 Int64
; 所以类型 *Int abcdefg 都等于 Int64
MsgBox, % NumGet(pointer, "*Int")
MsgBox, % NumGet(pointer, "Int64")
MsgBox, % NumGet(pointer, "abcdefg")
4.NumGet 不支持 UInt64 类型怎么办?
NumPut 支持 UInt64 类型,但 NumGet 不支持,怎么办呢?
下面这个例子,将演示 NumGet 如何读取 UInt64 类型的数并显示。
; 分配内存
VarSetCapacity(var, 8, 0)
; 将一个超出 Int64 最大范围,即大于 9223372036854775807 的数存入 var
NumPut(10000000000000000000, var, "UInt64")
; 以 Int64 作为类型取出这个数
out := NumGet(var, "Int64")
; 以 Int64 作为类型解释这个数并显示
MsgBox,% out
; 以 UInt64 作为类型解释这个数并显示
MsgBox,% int64ToUint64(out)
; ----------------------------------- 下面是转换函数 --------------------------------------
; 将 Int64 的值转为 UInt64
; 例如 -5 将转为 18446744073709551611
int64ToUint64(n)
{
; 2^64 = 18446744073709551616
return, n<0 ? SM_Add(n, "18446744073709551616") : n
}
; ----------------------------------- 下面是大数运算库 --------------------------------------
; https://github.com/aviaryan/autohotkey-scripts/blob/master/Functions/Maths.ahk
SM_Add(number1, number2, prefect=true){ ;Dont set Prefect false, Just forget about it.
;Processing
IfInString,number2,--
count := 2
else ifInString,number2,-
count := 1
else
count := 0
IfInString,number1,-
count+=1
;
n1 := number1
n2 := number2
StringReplace,number1,number1,-,,All
StringReplace,number2,number2,-,,All
;Decimals
dec1 := InStr(number1,".") ? StrLen(number1) - InStr(number1, ".") : 0
dec2 := InStr(number2,".") ? StrLen(number2) - InStr(number2, ".") : 0
if (dec1 > dec2){
dec := dec1
loop,% (dec1 - dec2)
number2 .= "0"
}
else if (dec2 > dec1){
dec := dec2
loop,% (dec2 - dec1)
number1 .= "0"
}
else
dec := dec1
StringReplace,number1,number1,.
StringReplace,number2,number2,.
;Processing
;Add zeros
if (StrLen(number1) >= StrLen(number2)){
loop,% (StrLen(number1) - StrLen(number2))
number2 := "0" . number2
}
else
loop,% (StrLen(number2) - StrLen(number1))
number1 := "0" . number1
n := StrLen(number1)
;
if count not in 1,3 ;Add
{
loop,
{
digit := SubStr(number1,1 - A_Index, 1) + SubStr(number2, 1 - A_Index, 1) + (carry ? 1 : 0)
if (A_Index == n){
sum := digit . sum
break
}
if (digit > 9){
carry := true
digit := SubStr(digit, 0, 1)
}
else
carry := false
sum := digit . sum
}
;Giving sign
if (InStr(n2,"-") and InStr(n1, "-"))
sum := "-" . sum
}
;SUBTRACT ******************
else
{
;Compare numbers for suitable order
numbercompare := SM_Greater(number1, number2, true)
if !(numbercompare){
mid := number2
number2 := number1
number1 := mid
}
loop,
{
digit := SubStr(number1,1 - A_Index, 1) - SubStr(number2, 1 - A_Index, 1) + (borrow ? -1 : 0)
if (A_Index == n){
StringReplace,digit,digit,-
sum := digit . sum
break
}
if InStr(digit, "-")
borrow:= true , digit := 10 + digit ;4 - 6 , then 14 - 6 = 10 + (-2) = 8
else
borrow := false
sum := digit sum
}
;End of loop ;Giving Sign
;
if InStr(n2,"--"){
if (numbercompare)
sum := "-" . sum
}else if InStr(n2,"-"){
if !(numbercompare)
sum := "-" . sum
}else ifInString,n1,-
if (numbercompare)
sum := "-" . sum
}
;End of Subtract - Sum
;End
if ((sum == "-")) ;LTrim(sum, "0") == ""
sum := 0
;Including Decimal
if (dec)
if (sum)
sum := SubStr(sum,1,StrLen(sum) - dec) . "." . SubStr(sum,1 - dec)
;Prefect
return, Prefect ? SM_Prefect(sum) : sum
}
SM_Greater(number1, number2, trueforequal=false){
IfInString,number2,-
IfNotInString,number1,-
return, true
IfInString,number1,-
IfNotInString,number2,-
return, false
if (InStr(number1, "-") and InStr(number2, "-"))
bothminus := true
number1 := SM_Prefect(number1) , number2 := SM_Prefect(number2)
; Manage Decimals
dec1 := (InStr(number1,".")) ? ( StrLen(number1) - InStr(number1, ".") ) : (0)
dec2 := (InStr(number2,".")) ? ( StrLen(number2) - InStr(number2, ".") ) : (0)
if (dec1 > dec2)
loop,% (dec1 - dec2)
number2 .= "0"
else if (dec2 > dec1)
loop,% (dec2 - dec1)
number1 .= "0"
StringReplace,number1,number1,.
StringReplace,number2,number2,.
; Compare Lengths
if (StrLen(number1) > StrLen(number2))
return,% (bothminus) ? (false) : (true)
else if (StrLen(number2) > StrLen(number1))
return,% (bothminus) ? (true) : (false)
else ;The final way out
{
stop := StrLen(number1)
loop,
{
if (SubStr(number1,A_Index, 1) > SubStr(number2,A_Index, 1))
return bothminus ? 0 : 1
else if (SubStr(number2,A_Index, 1) > SubStr(number1,A_Index, 1))
return bothminus ? 1 : 0
if (A_Index == stop)
return, (trueforequal) ? 1 : 0
}
}
}
SM_Prefect(number){
number .= "" ;convert to string if needed
number := RTrim(number, "+-")
if number=
return 0
if InStr(number, "-")
number := SubStr(number, 2) , negative := true
if InStr(number, "."){
number := Trim(number, "0")
if (SubStr(number,1,1) == ".") ;if num like .6767
number := "0" number
if (SubStr(number, 0) == ".") ;like 456.
number := SubStr(number, 1, -1)
return,% (negative) ? ("-" . number) : (number)
} ; Non-decimal below
else
{
if Trim(number, "0")
return negative ? ("-" . LTrim(number, "0")) : (LTrim(number, "0"))
else
return 0
}
}
5.可以将变量的值存为真正的数字,但通常别这么干!
除非你确切的知道自己在做什么,否则不要用 NumPut 把一个变量存为真正的数字。
下面这个例子,将演示为什么不要这么做。
hwnd := 0x34003500
; 有效!因为 hwnd 会被视作字符串 "0x34003500" 然后内部转为数字 0x34003500 再传入
DllCall("SwitchToThisWindow", "Ptr", hwnd, "Int", true)
; 分配空间
VarSetCapacity(hwnd2, 4, 0)
; 将 0x34003500 存入变量
NumPut(0x34003500, hwnd2, "Int")
; 无效!因为 hwnd2 会被视作字符串 "45" 然后内部转为数字 45 再传入
; 但实际需要的是数字 0x34003500
DllCall("SwitchToThisWindow", "Ptr", hwnd2, "Int", true)
挺好