type(v) for v1.1

Post your working scripts, libraries and tools for AHK v1.1 and older
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

type(v) for v1.1

Post by lexikos » 21 Feb 2014, 01:10

type(v)

Returns the type of a value: "Integer", "String", "Float" or "Object"

Code: Select all

; Object version - depends on current float format including a decimal point.
type(v) {
    if IsObject(v)
        return "Object"
    return v="" || [v].GetCapacity(1) ? "String" : InStr(v,".") ? "Float" : "Integer"
}

Code: Select all

; COM version - reports the wrong type for integers outside 32-bit range.
type(ByRef v) {
	if IsObject(v)
		return "Object"
	a := ComObjArray(0xC, 1)
	a[0] := v
	DllCall("oleaut32\SafeArrayAccessData", "ptr", ComObjValue(a), "ptr*", ap)
	type := NumGet(ap+0, "ushort")
	DllCall("oleaut32\SafeArrayUnaccessData", "ptr", ComObjValue(a))
	return type=3?"Integer" : type=8?"String" : type=5?"Float" : type
}
Note: Requires v1.1. It might work with v2, but I wouldn't recommend it.

Note: Results can be hard to predict due to binary number caching in v1. For instance, y := x + 0 can change the type of x from "String" to "Integer" or "Float".

Note: type(1.0) returns "String" because floating-point literals in v1 actually are strings. type(1.0+0) returns "Float".


Differentiating between object types

type() in v2 returns specific object classes, like FileObject or ComObject. This function doesn't. There are a couple of workarounds:
  • ComObjValue(x) != "" is true if x is a ComObject.
  • ObjGetCapacity(x) != "" is true if x is an Object (associative array).

User avatar
joedf
Posts: 8951
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: type(v) for v1.1

Post by joedf » 22 Feb 2014, 18:16

nice!
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]

obeeb
Posts: 140
Joined: 20 Feb 2014, 19:15

Re: type(v) for v1.1

Post by obeeb » 22 Feb 2014, 21:10

Great function.
Another note: It will incorrectly return float for integers bigger than 2147483647 (maximum number for signed 32 bit integers)

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: type(v) for v1.1

Post by lexikos » 23 Feb 2014, 02:38

Right. Technically, it returns the type which will be used if the value is passed to a COM object.

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: type(v) for v1.1

Post by lexikos » 10 Mar 2014, 04:10

I've updated the first post with a new version of the function which doesn't have the limitation obeeb mentioned:

Code: Select all

MsgBox % type("")     ; String
MsgBox % type(1)      ; Integer
MsgBox % type(1/1)    ; Float
MsgBox % type("1")    ; String
MsgBox % type(2**42)  ; Integer

type(v) {
    if IsObject(v)
        return "Object"
    return v="" || [v].GetCapacity(1) ? "String" : InStr(v,".") ? "Float" : "Integer"
}
This relies on the following:
  • Unlike a variable, an array element can't contain a string and a pure number simultaneously.
  • A field's "capacity" is the size of string it can hold before it needs to expand. If it doesn't hold a string it has no capacity.
  • SetFormat hasn't been used to remove the decimal point from the floating-point string format. (If var is float has the same limitation, since it always converts the value to a string for analysis.)
v="" || [v].GetCapacity(1) could be replaced with [v].GetCapacity(1) != "" or v="" || [v].GetAddress(1) to the same effect.

MrDoge
Posts: 159
Joined: 27 Apr 2020, 21:29

Re: type(v) for v1.1

Post by MrDoge » 14 Dec 2021, 23:31

added support for VarSetCapacity "Buffer"
the trick is that COPY of Buffer is different from Byref Buffer, while copy of String is identical

Code: Select all

if (v "") { ;concatenation works too, you can MsgBox % Buffer, but a Buffer won't concat
    return "String" ;non empty String, we cover empty String down below
} else {
    return "Buffer"
}
and added support for Array
isArray() by jeeswg, then I edited it..

Type.ahk

Code: Select all

; /*
; TESTS
emptyString:=""
assert(type(emptyString), "String") ;String

VarSetCapacity(structZero, 0)
; MsgBox % VarSetCapacity(structZero) ;0
assert(type(structZero), "String") ;String..
; THEY'RE THE SAME ADDRESS &emptyString==&structZero==5369930888, seeing "" can mean that the Buffer has been freed, and no one uses an empty Buffer
; MsgBox % &emptyString "`n" &structZero




VarSetCapacity(struct1, 16)
; Msgbox % VarSetCapacity(struct1) ;16
assert(type(struct1), "Buffer") ;Buffer


VarSetCapacity(struct2, 1)
; Msgbox % VarSetCapacity(struct2) ;6 even if 1
assert(type(struct2), "Buffer") ;Buffer




randomBuff(len, Byref RandomBuffer) {
    static hModule_Advapi32 := DllCall("LoadLibrary", "Str", "Advapi32.dll", "Ptr")
    static Proc_RtlGenRandom := DllCall("GetProcAddress", "Ptr", hModule_Advapi32, "AStr", "SystemFunction036", "Ptr")
    RandomBufferLength:=Ceil(0.75*len)
    VarSetCapacity(RandomBuffer, RandomBufferLength)
    ok:=DllCall(Proc_RtlGenRandom, "Ptr", &RandomBuffer, "UInt", RandomBufferLength)
}
randomBuff(100, RandomBuffer)
assert(type(RandomBuffer), "Buffer")


assert(type("abc"), "String")
assert(type("1"), "String")
assert(type(1.1), "String")


assert(type(1), "Integer")


assert(type(1/1), "Float")


assert(type([]), "Array")
assert(type([1,2,3]), "Array")
assert(type({1:1, 3:3, 4:4}), "Object")
assert(type({1:"a", 3:"c", x:"x"}), "Object")
assert(type({1:"a", "2":"b", 3:"c"}), "Object")

assert(type(1), "fwiefowief") ;test if my assert function works :)

assert(actual, expected) {
    if !(actual==expected)
        MsgBox % "Line " Exception("", -1).Line " Failed`nactual:      " actual "`nexpected: " expected
    ;https://www.autohotkey.com/board/topic/71487-can-a-function-grab-the-calling-lines-line-number/#post_id_453358
}
; */

; "0 size String" v=="" && [v].GetCapacity(1)==0 && VarSetCapacity()==0
; "0 size Buffer" v=="" && [v].GetCapacity(1)==0 && VarSetCapacity()==0
; "16 size Buffer with no string representation" v=="" && [v].GetCapacity(1)==0 && VarSetCapacity()>0

; "String" v!="" && [v].GetCapacity(1)>0 && VarSetCapacity()>0

; Byref "Buffer" v!="" && [v].GetCapacity(1)>0 && VarSetCapacity()>0
; I think Byref Buffer could be v=="" (by chance)
; the trick is that COPY of Buffer is different from Byref Buffer, while copy of String is identical
; COPY OF BUFFER v=="" && [v].GetCapacity(1)==0 && VarSetCapacity()>0

; Number v!="" && [v].GetCapacity(1)=="" && VarSetCapacity()==0

type(Byref v) { ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=2306
    if IsObject(v) {
        ; if isArray() https://www.autohotkey.com/boards/viewtopic.php?f=76&t=64332&p=434396#p434396
        if (!ObjCount(v) || ObjMinIndex(v) == 1 && ObjMaxIndex(v) == ObjCount(v) && v.Clone().Delete(1, v.MaxIndex()) == ObjCount(v)) {
            return "Array"
        } else {
            return "Object"
        }
    } else {
        objCapacity:=[v].GetCapacity(1)
        if (objCapacity>0) {
            ;strings will get perfectly copied, but Buffer won't
            ; if ([copy:=v].GetCapacity(1)>0) {
            if (v "") { ;concatenation works too, you can MsgBox % Buffer, but a Buffer won't concat
                return "String" ;non empty String, we cover empty String down below
            } else {
                return "Buffer" ;Buffer with string representation, but can't concat
            }
        } else if (objCapacity==0) {
            if (VarSetCapacity(v)) {
                return "Buffer" ; 16 size Buffer with no String representation
            } else {
                return "String" ; 0 size String And 0 size Buffer, they're the same thing
            }

        } else {
            if (InStr(v,".")) {
                return "Float"
            } else {
                return "Integer"
            }
        }

    }
}
ternary version:

Code: Select all

type(Byref v) { ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=2306
    return IsObject(v) ? ((!ObjCount(v) || ObjMinIndex(v) == 1 && ObjMaxIndex(v) == ObjCount(v) && v.Clone().Delete(1, v.MaxIndex()) == ObjCount(v)) ? "Array" : "Object") : ((objCapacity:=[v].GetCapacity(1))>0 ? (v "" ? "String" : "Buffer") : objCapacity==0 ? (VarSetCapacity(v) ? "Buffer" : "String") : (InStr(v,".") ? "Float" : "Integer"))
}
version with "uninitialized variable" added:

Code: Select all

; Double-deref "" is a fatal error in general, try (MsgBox % %v%)
; so I can 'not support it', I also cannot support it...
; check before Double-deref:
; (v=="" ? "fatal error" : type(%v%))

MsgBox % type(a) ;"uninitialized variable"
MsgBox % type(a:="") ;"String"
MsgBox % (v=="" ? "fatal error" : type(%v%)) ;"fatal error"
v:="abc"
MsgBox % (v=="" ? "fatal error" : type(%v%)) ;"uninitialized variable"

type(Byref v) { ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=2306
    ; Array, Object, String, Buffer, Float, Integer, uninitialized variable
    static Func_mParam := 3*A_PtrSize, Var_mAttrib := 8 + 3*A_PtrSize + 1
    , Var_mAliasFor := 8 + A_PtrSize
    , VAR_ATTRIB_UNINITIALIZED := 0x04
    , funcAddress := &Func("type")

    if IsObject(v) {
        ; if isArray() https://www.autohotkey.com/boards/viewtopic.php?f=76&t=64332&p=434396#p434396
        if (!ObjCount(v) || ObjMinIndex(v) == 1 && ObjMaxIndex(v) == ObjCount(v) && v.Clone().Delete(1, v.MaxIndex()) == ObjCount(v)) {
            return "Array"
        } else {
            return "Object"
        }
    } else {
        objCapacity:=[v].GetCapacity(1)
        if (objCapacity>0) {
            ;strings will get perfectly copied, but Buffer won't
            ; if ([copy:=v].GetCapacity(1)>0) {
            if (v "") { ;concatenation works too, you can MsgBox % Buffer, but a Buffer won't concat
                return "String" ;non empty String, we cover empty String down below
            } else {
                return "Buffer" ;Buffer with string representation, but can't concat
            }
        } else if (objCapacity==0) {
            if (VarSetCapacity(v)) {
                return "Buffer" ; 16 size Buffer with no String representation
            } else {
                ; click the spoiler at the bottom in https://www.autohotkey.com/boards/viewtopic.php?f=76&t=89664&start=20#p395439
                if (return NumGet(NumGet(NumGet(NumGet(funcAddress + Func_mParam)) + Var_mAliasFor) + Var_mAttrib, "uchar") & VAR_ATTRIB_UNINITIALIZED) {
                    return "uninitialized variable"
                } else {
                    return "String" ; 0 size String And 0 size Buffer, they're the same thing
                }
            }

        } else {
            if (InStr(v,".")) {
                return "Float"
            } else {
                return "Integer"
            }
        }

    }
}
___
I'm using this in Array_p()
to print a Buffer like this : < 255 0 1 3 >

I'm using recursion in a Byref function to print deep arrays
I have a question :
; using [Value24][1] because Value24 breaks Byref, why does it break Byref ?
(Ctrl+F to find this in code)

Code: Select all

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance, force
ListLines Off
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
SetBatchLines, -1
#KeyHistory 0

doPrint:=true
; doPrint:=false

emptyString:=""
printIfTrue(Array_p(emptyString))

VarSetCapacity(structZero, 0)
; MsgBox % VarSetCapacity(structZero) ;0
printIfTrue(Array_p(structZero))
; THEY'RE THE SAME ADDRESS &emptyString==&structZero==5369930888, seeing "" can mean that the Buffer has been freed, and no one uses an empty Buffer
; MsgBox % &emptyString "`n" &structZero




VarSetCapacity(struct1, 16)
; Msgbox % VarSetCapacity(struct1) ;16
printIfTrue(Array_p(struct1))


VarSetCapacity(struct2, 1)
; Msgbox % VarSetCapacity(struct2) ;6 even if 1
printIfTrue(Array_p(struct2))




randomBuff(len, Byref RandomBuffer) {
    static hModule_Advapi32 := DllCall("LoadLibrary", "Str", "Advapi32.dll", "Ptr")
    static Proc_RtlGenRandom := DllCall("GetProcAddress", "Ptr", hModule_Advapi32, "AStr", "SystemFunction036", "Ptr")
    RandomBufferLength:=Ceil(0.75*len)
    VarSetCapacity(RandomBuffer, RandomBufferLength)
    ok:=DllCall(Proc_RtlGenRandom, "Ptr", &RandomBuffer, "UInt", RandomBufferLength)
}
randomBuff(100, RandomBuffer)
printIfTrue(Array_p(RandomBuffer))


printIfTrue(Array_p("abc"))
printIfTrue(Array_p("1"))
printIfTrue(Array_p(1.1))


printIfTrue(Array_p(1))


printIfTrue(Array_p(1/1))


printIfTrue(Array_p([]))
printIfTrue(Array_p([1,2,3]))
printIfTrue(Array_p({1:1, 3:3, 4:4}))
printIfTrue(Array_p({1:"a", 3:"c", x:"x"}))
printIfTrue(Array_p({1:"a", "2":"b", 3:"c"}))

ExitApp

printIfTrue(toPrint) {
    global doPrint
    if (doPrint) {
        MsgBox % toPrint
    }
}

type(Byref v) { ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=2306
    return IsObject(v) ? ((!ObjCount(v) || ObjMinIndex(v) == 1 && ObjMaxIndex(v) == ObjCount(v) && v.Clone().Delete(1, v.MaxIndex()) == ObjCount(v)) ? "Array" : "Object") : ((objCapacity:=[v].GetCapacity(1))>0 ? (v "" ? "String" : "Buffer") : objCapacity==0 ? (VarSetCapacity(v) ? "Buffer" : "String") : (InStr(v,".") ? "Float" : "Integer"))
}

;Byref for Buffer() < 255 0 1 3 >
Array_p(Byref Arr21, delim:=", ") {
    OutPut:=""

    theType:=type(Arr21)
    switch (theType) {
        case "Array":
            ; For Key23, Value24 in Arr21 { ;we cannot use this because it breaks Byref, why does it break Byref ?
            For Key23 in Arr21 {
                Output .= delim Array_p(Arr21[Key23], delim)
            }
            OutPut := "[" SubStr(OutPut, StrLen(delim)+1) "]" ;remove the first ", "
        case "Object":
            ; using [Value24][1] because Value24 breaks Byref, why does it break Byref ?
            For Key23, Value24 in Arr21 {
                Output .= delim (type(Key23) == "String" ? """" Key23 """" : Key23) ":" Array_p([Value24][1], delim)
            }
            OutPut := "{" SubStr(OutPut, StrLen(delim)+1) "}" ;remove the first ", "
        case "Integer", "Float":
            Output .= Arr21
        case "String":
            Output .= """" Arr21 """"
        case "Buffer":
            Size:=VarSetCapacity(Arr21)
            offset:=0
            OutPut.="< "
            while (offset < Size) {
                OutPut.=NumGet(Arr21, offset, "UChar") " "
                offset++
            }
            OutPut.=">"
    }

    Return OutPut
}

f3::Exitapp

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: type(v) for v1.1

Post by Chunjee » 12 May 2022, 15:07

Sorry I'm not sure what changed

v1.1.33 and v1.1.34.01

Code: Select all

; 1.1.34.01
MsgBox % type("")     ; String
MsgBox % type(1)      ; Integer
MsgBox % type(1/1)    ; String 🤔
MsgBox % type("1")    ; String
MsgBox % type(2**42)  ; Integer

MrDoge
Posts: 159
Joined: 27 Apr 2020, 21:29

Re: type(v) for v1.1

Post by MrDoge » 12 May 2022, 17:38

I tested on every ahk version U64, till it broke at AutoHotkey_1.1.28.02
I got "Float"
please paste full code (the specifc type() function you used)

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: type(v) for v1.1

Post by lexikos » 27 Nov 2022, 01:08

I came across some information about the implementation of RTTI (run-time type information) for Visual C++ and realized that it would be easy to get the C++ class name of an object in script:

Code: Select all

CppClassName(obj) {
    if !IsObject(obj)
        throw Exception("Parameter #1 must be an object", -1, obj)
    vfptr := NumGet(&obj, "ptr")
    completeObjectLocator := NumGet(vfptr - A_PtrSize, "ptr")
    signature := NumGet(completeObjectLocator + 0, "int")
    typeDescriptor := NumGet(completeObjectLocator + 12, "int")
    if (signature = 1) { ; x64: typeDescriptor is an offset from the image base
        imagebase := completeObjectLocator - NumGet(completeObjectLocator + 20, "int") ; pSelf
        typeDescriptor += imagebase
    }
    name := StrGet(typeDescriptor + A_PtrSize*2, "cp0")
    if SubStr(name, 1, 4) == ".?AV" && SubStr(name, -1) == "@@"
        name := SubStr(name, 5, -2)
    return name
}

Code: Select all

; Examples
MsgBox % CppClassName([])
MsgBox % CppClassName(ComObj())
MsgBox % CppClassName("".base)
MsgBox % CppClassName(Func("Func").Bind())
MsgBox % CppClassName(InputHook())
The technique is fairly safe as long as AutoHotkey is compiled with Visual C++ and the object has runtime type information. With the current implementation (which is unlikely to ever change for v1), all objects have runtime type information. However, classes without any unique virtual functions may return the class name of their base class (there might not be any such classes in v1).

Note that the C++ class names are subject to change, although it's unlikely that I will change them for v1.

Thinking about it more, I remembered that the debugger also shows class names. I had forgotten that v1.1.26 implemented explicitly defined class names that aren't derived from the C++ class names. ListVars also shows them. Retrieving these class names is even simpler. It relies on an internal function being present at a specific offset within the virtual function table, but this is unlikely to change for v1 (and if it does, maybe I'll have ported the Type function):

Code: Select all

ClassName(obj) {
    if !IsObject(obj)
        throw Exception("Parameter #1 must be an object", -1, obj)
    return DllCall(NumGet(NumGet(&obj)+A_PtrSize*8), "ptr", &obj, "str")
}

Code: Select all

; Examples
MsgBox % ClassName([])
MsgBox % ClassName(ComObj())
MsgBox % ClassName("".base)
MsgBox % ClassName(Func("Func").Bind())
MsgBox % ClassName(InputHook())

Post Reply

Return to “Scripts and Functions (v1)”