NumGet、NumPut 示例与细节解释

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)

 

给TA捐赠
共{{data.count}}人
人已捐赠
其他

个人文件管理 2022年4月7日

2022-4-7 13:48:40

其他

用简短易记的用户名和密码连接群晖NAS的SFTP文件服务 2022.04.08

2022-4-8 16:39:20

2 条回复 A文章作者 M管理员
  1. Tmz

    挺好

  2. dbgba
    dbgba给您打赏了¥10
个人中心
今日签到
有新私信 私信列表
搜索