#NoEnv /* Class Library - add OOP to your programs I want to send a BIG, BIG thanks to Lexikos for the template that make this possible. This library is used by all classes to manipulate class data */ /* DllCalls */ ;returns a memory address to a Dll function (used to speed up DllCalls) Class_LoadDllFunction(ThisFile, ThisFunction) { ;note: this is not my code, and sadly I have forgotten who wrote it ;contact me and I'll add your name here to give proper credit if !hModule := DllCall("GetModuleHandle", "uint", &ThisFile, "uint") { hModule := DllCall("LoadLibrary", "uint", &ThisFile, "uint") } return DllCall("GetProcAddress", "uint", hModule, "uint", &ThisFunction, "uint") } Class_Alloc(size) { ; static GlobalAlloc static HeapAlloc, ProcessHeap ; if (!GlobalAlloc) if (!HeapAlloc) { ; GlobalAlloc := Class_LoadDllFunction("Kernel32", "GlobalAlloc") HeapAlloc := Class_LoadDllFunction("Kernel32", "HeapAlloc") ProcessHeap := DllCall("GetProcessHeap") } if size is integer { ;if a number (alone) is specified, add 8 to size (+8 is for "class values") size += 8 } else { start := subStr(size, 1, 1) if (start = "a") { ;specify "a" followed by a number to allocate bytes ;ex. a16 will allocate 16 bytes ;remove leading "a" StringTrimLeft, size, size, 1 } else return } ;GMEM_ZEROINIT = 0x40 - Initializes memory contents to zero. ; return DllCall(GlobalAlloc, "uint", 0x40, "uint", size) ;HEAP_ZERO_MEMORY 0x8 - The allocated memory will be initialized to zero. return DllCall(HeapAlloc, "uint", ProcessHeap, "uint", 0x8, "uint", size, "uint") } Class_ReAlloc(addr, size) { ; static GlobalReAlloc static HeapReAlloc, ProcessHeap ; if (!GlobalReAlloc) if (!HeapReAlloc) { ; GlobalReAlloc := Class_LoadDllFunction("Kernel32", "GlobalReAlloc") HeapReAlloc := Class_LoadDllFunction("Kernel32", "HeapReAlloc") ProcessHeap := DllCall("GetProcessHeap") } if size is integer { ;if a number (alone) is specified, add 8 to size (+8 is for "class values") size += 8 } else { start := subStr(size, 1, 1) if (start = "a") { ;specify "a" followed by a number to allocate bytes ;ex. a16 will allocate 16 bytes ;remove leading "a" StringTrimLeft, size, size, 1 } else return } ;GMEM_ZEROINIT = 0x40 - Causes the additional memory contents to be ;initialized to zero if the memory object is growing in size. ;GMEM_MOVEABLE = 0x2 - If the memory is a GMEM_FIXED memory block ;and this flag is not specified, the memory can only be reallocated in place. ; return DllCall(GlobalReAlloc, "uint", addr, "uint", size, "uint", 0x42) ;HEAP_ZERO_MEMORY 0x8 - If the reallocation request is for a larger size, ;the additional region of memory beyond the original size be initialized ;to zero. The contents of the memory block up to its original size are unaffected. return DllCall(HeapReAlloc, "uint", ProcessHeap, "uint", 0x8, "uint", addr, "uint", size, "uint") } Class_Free(addr) { ; static GlobalFree static HeapFree, ProcessHeap ; if (!GlobalFree) if (!HeapFree) { ; GlobalFree := Class_LoadDllFunction("Kernel32", "GlobalFree") HeapFree := Class_LoadDllFunction("Kernel32", "HeapFree") ProcessHeap := DllCall("GetProcessHeap") } ; DllCall(GlobalFree, "uint", addr) DllCall(HeapFree, "uint", ProcessHeap, "uint", 0x0, "uint", addr) } ;returns the number of bytes allocated by addr Class_getMemorySize(addr) { static HeapSize, ProcessHeap if (!HeapSize) { HeapSize := Class_LoadDllFunction("Kernel32", "HeapSize") ProcessHeap := DllCall("GetProcessHeap") } return DllCall(HeapSize, "uint", ProcessHeap, "uint", 0x0, "uint", addr) } ;returns the null-terminated string at address Class_toString(addr) { static MulDiv if (!MulDiv) MulDiv := Class_LoadDllFunction("Kernel32", "MulDiv") ;MulDiv returns
unmodified, but "str" causes AutoHotkey to ;interpret it as a string. Safe even if
is NULL (0). return DllCall(MulDiv, "int", addr, "int",1,"int",1, "str") } Class_CopyMemory(Destination, Source, length) { static RtlCopyMemory ;Todo: find a working way to use CopyMemory if (!RtlCopyMemory) RtlCopyMemory := Class_LoadDllFunction("Kernel32", "RtlMoveMemory") DllCall(RtlCopyMemory, "uint", Destination, "uint", Source, "uint", length) } ;copies the string in to destination address ;According to lstrcpy Function docs: ;The buffer (destination) must be large enough to contain the string, ;including the terminating null character. Class_CopyString(Destination, ByRef Source) { static lstrcpy if (!lstrcpy) lstrcpy := Class_LoadDllFunction("Kernel32", "lstrcpy") DllCall(lstrcpy, "uint", Destination, "uint", &Source) } ;performs a shallow copy of the object, returning the address for the copied Class object ;used by container classes to perform a quick copy /* Class_ShallowCopy(ClassObject) { MemorySize := Class_getMemorySize(ClassObject) if TheClone := Class_alloc("a" MemorySize) { Class_CopyMemory(TheClone, ClassObject, MemorySize) } return TheClone } */ ;Moves a block of memory from one location to another. ;Note: The source and destination blocks may overlap. Class_MoveMemory(Destination, Source, Length) { static RtlMoveMemory if (!RtlMoveMemory) RtlMoveMemory := Class_LoadDllFunction("Kernel32", "RtlMoveMemory") DllCall(RtlMoveMemory, "uint", Destination, "uint", Source, "uint", Length) } Class_MoveLeft(DataObject, Start, End, Count = 1) { ;move items Start, inclusive, to End, exclusive, left words ;indexes are in Words (4 bytes), and start at 0 if (Start = End) return Source := DataObject + 4 * Start Destination := Source - 4 * Count Length := 4 * (End - Start) Class_MoveMemory(Destination, Source, Length) } Class_MoveRight(DataObject, Start, End, Count = 1) { ;move items Start, inclusive, to End, exclusive, right words ;indexes are in Words (4 bytes), and start at 0 if (Start = End) return Source := DataObject + 4 * Start Destination := Source + 4 * Count Length := 4 * (End - Start) Class_MoveMemory(Destination, Source, Length) } ;this function mimics the behavior of if val [not] in MatchList ;the function returns the index for the found item ; (or 0 if not found) ;if the item is found, ErrorLevel is set the the matching substring ; (as reflected in the Match list) Class_in(Value, MatchList, Delimiters = ",", OmitChars = "") { Loop, Parse, MatchList, %Delimiters%, %OmitChars% { if (A_LoopField = Value) { ;store the match (as reflected in the Match list) ErrorLevel := A_LoopField return A_Index } } return false } ;returns true if value is not locked (i.e. ready to delete) ;returns false if value is locked (i.e. not ready to delete) ;(call only in %ClassName%_destroy function) Class_readyToDelete(ClassObject) { LockCount := Class_getLockCount(ClassObject) if (LockCount > 0) { Class_decreaseLockCount(ClassObject) LockCount-- } return (LockCount = 0) } ;These functions are usable by any class object ;the setters should only be called within the class ; (not by the user of the class) ;Class value has this structure: "ClassName(Options)" ;Options is a comma-delimented list (there can be spaces between items) ;however, such spaces only waste memory (a small bit, granted), and serve no purpose Class_getClass(ClassObject) { if (!ClassObject) return ;returns the ClassName as a String return Class_toString(NumGet(ClassObject+0)) } ;returns ClassName used to call Class functions dynamically Class_getClassName(ClassObject) { if (!ClassObject) return Class := Class_getClass(ClassObject) RegExMatch(Class, "^(?\w+)", Class) return ClassName } ;returns the ClassName if the ClassObject has the specified ClassOption ;otherwise, returns the empty string Class_hasClassOption(ClassObject, ClassOption) { static ClassNameRegEx := "^(?\w++)(?:\((?[^\)]*)\))?(?: implements \((?[^\)]*)\))?$" /* ;formatted for easy viewing ClassNameRegEx := "^(?\w++)" . "(?:\((?[^\)]*)\))?" . "(?: implements \((?[^\)]*)\))?$" */ if (!ClassObject) return Class := Class_getClass(ClassObject) if !RegExMatch(Class, ClassNameRegEx, Class) return return Class_in(ClassOption, ClassOptions, ",", " `t") ? ClassName : false } ;returns the ClassName if the ClassObject implements the specified Interface ;otherwise, returns the empty string Class_implements(ClassObject, Interface) { static ClassNameRegEx := "^(?\w++)(?:\((?[^\)]*)\))?(?: implements \((?[^\)]*)\))?$" /* ;formatted for easy viewing ClassNameRegEx := "^(?\w++)" . "(?:\((?[^\)]*)\))?" . "(?: implements \((?[^\)]*)\))?$" */ if (!ClassObject) return Class := Class_getClass(ClassObject) if !RegExMatch(Class, ClassNameRegEx, Class) return return Class_in(Interface, ClassImplements, ",", " `t") ? ClassName : false } Class_isWrapper(ClassObject) { return Class_hasClassOption(ClassObject, "Wrapper") } ;dynamically unwrap the specified Class object Class_unwrapThis(ClassObject) { if (!ClassObject) return if ClassName := Class_isWrapper(ClassObject) return %ClassName%_getValue(ClassObject) } Class_isCloneable(ClassObject) { return Class_hasClassOption(ClassObject, "Cloneable") } Class_constant(ClassNameOrObject, variable, setValue = "") { if (!ClassNameOrObject) return if ClassNameOrObject is integer ClassName := Class_getClassName(ClassNameOrObject) else ClassName := ClassNameOrObject return %ClassName%_constant(variable, setValue) } ;returns the size (in bytes) for the specified type Class_sizeOf(type) { if (type = "Char" || type = "UChar") return 1 else if (type = "Short" || type = "UShort") return 2 else if (type = "Int64" || type = "UInt64" || type = "double") return 8 else return 4 } ;function to test if two objects are equal (by comparing value-by-value) Class_equals(o1, o2) { if (o1 = o2) return true if (!o1 || !o2) { ;if either is not object, only equal if neither is an object return !o1 && !o2 } ;static ClassValues = 8 index := 8 ClassName := Class_getClassName(o1) ;case-insensitive not equals if !(Class_getClassName(o2) = ClassName) { ;different classes return false } ;compare built-in values BuiltInValues := %ClassName%_getBuiltInValues() Loop, Parse, BuiltInValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue if (type = "data") { dataIndex := index index += 4 continue } ;index is ByRef (and updated to the index right after the present one) if !Class_equals_private(o1, o2, index, type) return false } ;compare user-defined values UserDefinedValues := %ClassName%_getUserDefinedValues() Loop, Parse, UserDefinedValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue ;index is ByRef (and updated to the index right after the present one) if !Class_equals_private(o1, o2, index, type) return false } if (dataIndex) { ;used in container classes o1data := NumGet(o1+dataIndex) o2data := NumGet(o2+dataIndex) size := %ClassName%_size(o1) size2 := %ClassName%_size(o2) if (size != size2) return false if (size = 0) return true DataValues := %ClassName%_getDataValues() StringSplit, DataValues, DataValues, `, index := 0 Loop, %size% { Loop, %DataValues0% { if (!DataValues%A_Index%) continue if !Class_equals_private(o1data, o2data, index, DataValues%A_Index%) return false } } } return true } ;compares the specified value in and for equality ;index MUST be a zero-based offset (0 being the class object's address) ;sets index to the offset for the next item Class_equals_private(o1, o2, ByRef index, Type) { ;ignore this value if inStr(type, "NoEquals&") { endOptions := inStr(type, "&", false, 0) RegExMatch(type, "\w+", match, endOptions + 1) ;adjusts the index, and then returns index += Class_sizeOf(match) return true } if endOptions := inStr(type, "&", false, 0) type := subStr(type, endOptions + 1) if (type = "string") { ;case-insensitive compare string1 := Class_toString(NumGet(o1+index)) string2 := Class_toString(NumGet(o2+index)) index += 4 return string1 = string2 } else if (type = "SString") { ;case-sensitive compare string1 := Class_toString(NumGet(o1+index)) string2 := Class_toString(NumGet(o2+index)) index += 4 return string1 == string2 } else if (type = "obj" || type = "robj" || type = "ptr") { ;dynamically compare the objects obj1 := NumGet(o1+index) obj2 := numGet(o2+index) index += 4 return Class_equalsThis(obj1, obj2) } else if (type = "sptr") { ;a shallow pointer to an object - compare as a number ptr1 := NumGet(o1+index) ptr2 := NumGet(o2+index) index += 4 return ptr1 = ptr2 } else { ;basic type - compare as a number value1 := NumGet(o1+index, 0, type) value2 := NumGet(o2+index, 0, type) ;done to set index to new address (regardless of type) ; index := NumPut(value1, o1+index, 0, type) - o1 index += Class_sizeOf(type) return value1 = value2 } } ;dynamically compares two Class objects ;calls the equals function for the first object ;returns true (1) if the two are equal and false (0) if not ;if the class doesn't have an equals function, the objects are considered NOT equal Class_equalsThis(o1, o2) { if (o1 = o2) return true if (!o1 || !o2) return !o1 && !o2 if ClassName := Class_getClassName(o1) return %ClassName%_equals(o1, o2) } ;clones the specified ClassObject ;not meant to be called externally ;call Class_cloneThis(ClassObject, DeepCopy = true) instead Class_clone(ClassObject, DeepCopy) { if (!ClassObject) return if !TheClone := Class_Alloc("a" Class_getMemorySize(ClassObject)) return 0 ;Store "Class values" minus the lock count ;(lock count is zero) ClassName := NumGet(ClassObject+0) BuiltInSize := NumGet(ClassObject+4, 0, "uchar") UserDefinedSize := NumGet(ClassObject+5, 0, "uchar") NumPut(ClassName, TheClone+0) NumPut(BuiltInSize, TheClone+4, 0, "uchar") NumPut(UserDefinedSize, TheClone+5, 0, "uchar") ;static ClassValues = 8 index := 8 ClassName := Class_getClassName(ClassObject) ;clone built-in values BuiltInValues := %ClassName%_getBuiltInValues() Loop, Parse, BuiltInValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue if (type = "data") { dataIndex := index index += 4 continue } index := Class_clone_private(TheClone, ClassObject, index, DeepCopy, type) } ;clone user-defined values UserDefinedValues := %ClassName%_getUserDefinedValues() Loop, Parse, UserDefinedValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue index := Class_clone_private(TheClone, ClassObject, index, DeepCopy, type) } if (dataIndex) { ;used in container classes DataValues := %ClassName%_getDataValues() data := NumGet(ClassObject+dataIndex) ;clone the data values if cloneData := Class_alloc("a" Class_getMemorySize(data)) { ;associated the clone data with the clone object NumPut(cloneData, TheClone+dataIndex) size := %ClassName%_size(ClassObject) StringSplit, DataValues, DataValues, `, index := 0 Loop, %size% { Loop, %DataValues0% { if (!DataValues%A_Index%) continue index := Class_clone_private(cloneData, data , index, DeepCopy, DataValues%A_Index%) } } } } return TheClone } ;clones the specified value in to ;returns the offset for the next item (0 being the class object's address) Class_cloneValue(TheClone, ClassObject, index, DeepCopy, Type = "uint") { ;ignores BitOffset index := Class_getOffset(ClassObject, index, BitOffset) return Class_clone_private(TheClone, ClassObject, index, DeepCopy, Type) } ;clones the specified value in to ;index MUST be a zero-based offset (0 being the class object's address) Class_clone_private(TheClone, ClassObject, index, DeepCopy, Type) { ;ignore this value if inStr(type, "NoClone&") { endOptions := inStr(type, "&", false, 0) RegExMatch(type, "\w+", match, endOptions + 1) ;adjusts the index, and then returns return index + Class_sizeOf(match) } if endOptions := inStr(type, "&", false, 0) type := subStr(type, endOptions + 1) if (type = "string" || type = "SString") { if addr := NumGet(ClassObject+index) { ;there is a previous string value ;copy regardless if Shallow, Deep, or "Settings only" copy size := Class_getMemorySize(addr) ;Allocate buffer for string. newString := Class_Alloc("a" size) ;Copy string into buffer. Class_CopyMemory(newString, addr, size) ;Store pointer to string. NumPut(newString, TheClone+index) } return index + 4 } else if (type = "obj" || type = "robj") { if (!DeepCopy) { ;shallow copy, only copy the address ;if NULL (0), there is no object to clone if addr := NumGet(ClassObject+index) NumPut(addr, TheClone+index) return index + 4 } else if (DeepCopy != -1) { ;Deep copy - recursively clone ;if NULL (0), there is no object to clone if addr := NumGet(ClassObject+index) NumPut(Class_cloneThis(addr), TheClone+index) return index + 4 } ;else, "settings only" copy ;Class' clone function provides the functionality } else if (type = "ptr" || type = "sptr") { ;a pointer to an object - clones as a pointer (a uint) if addr := NumGet(ClassObject+index) NumPut(addr, TheClone+index) return index + 4 } else { ;basic type, copy value over value := NumGet(ClassObject+index, 0, type) return NumPut(value, TheClone+index, 0, type) - TheClone } } ;dynamically clones a Class object ;returns the address for the ClassObject if the Class isn't cloneable ;(to allow setting this functions return directly in the clone) ;in this case, the clone will only be a partial clone Class_cloneThis(ClassObject, DeepCopy = true) { if (!ClassObject) return 0 if ClassName := Class_isCloneable(ClassObject) return %ClassName%_clone(ClassObject, DeepCopy) return ClassObject } ;destroys the specified class object ;frees up both the built-in values and user-defined values (via dynamic calls) ;MUST have a %ClassName%_getBuiltInValues() function which returns ;a comma-delimited list of the types (used in the SETTERS) for the Class values ;Likewise, a %ClassName%_getUserDefinedValues() function for the user-defined values ;the types MUST be in the same order as stored - ascending ;ex. b1, b1b, b2, b2a, etc. Class_destroy(ClassObject) { if (!ClassObject) return if !Class_readyToDelete(ClassObject) return false ;static BuiltInValues = 8 index := ClassObject + 8 ClassName := Class_getClassName(ClassObject) ;Clear built-in values BuiltInValues := %ClassName%_getBuiltInValues() data := "data" if data in %BuiltInValues% size := %ClassName%_size(ClassObject) Loop, Parse, BuiltInValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue if (type = "data") { data := NumGet(index+0) NumPut(0, index+0) index += 4 continue } index := Class_destroy_private(ClassObject, index, type) } ;Clear user-defined values UserDefinedValues := %ClassName%_getUserDefinedValues() Loop, Parse, UserDefinedValues, `, , %A_Tab%%A_Space% { if !type := A_LoopField continue index := Class_destroy_private(ClassObject, index, type) } ;object has a "data" member (used in container classes) if (size) { DataValues := %ClassName%_getDataValues() StringSplit, DataValues, DataValues, `, index := data Loop, %size% { Loop, %DataValues0% { if (!DataValues%A_Index%) continue index := Class_destroy_private(data, index, DataValues%A_Index%) } } } if (data) Class_free(data) ;Clear "Class values" {class name, built-in size, user-defined size} NumPut(0, ClassObject+0) NumPut(0, ClassObject+4, 0, "uchar") NumPut(0, ClassObject+5, 0, "uchar") ;Free Class_Free(ClassObject) return true } ;clears the specified value in ;index MUST be a zero-based offset ;(0 being the class object's first value - ClassObject + 0) Class_destroy_private(ClassObject, index, Type) { if (type = "string" || type = "SString") { if addr := NumGet(index+0) { ;there is a previous string value ;free the memory Class_free(addr) ;store a zero in its place NumPut(0, index+0) } return index + 4 } else if (type = "obj" || type = "robj") { if addr := NumGet(index+0) { ;destroy the previous Class object Class_destroyThis(addr) ;store a zero in its place NumPut(0, index+0) } return index + 4 } else if (type = "ptr" || type = "sptr") { ;a pointer to an object - destroys as a pointer (a uint) return NumPut(0, index+0) } else { ;basic type, store a zero return NumPut(0, index+0, 0, type) } } ;calls the destroy function for the ClassObject dynamically ;(for when you don't know the ClassName in advance) Class_destroyThis(ClassObject) { if (!ClassObject) return if (ClassName := Class_getClassName(ClassObject)) return %ClassName%_destroy(ClassObject) } Class_hasLockCount(ClassObject) { return true } /* Class_hasLockCount(ClassObject) { ;returns whether ClassObject has a LockCount ;all Classes except Class_Nodes (e.g. CDDL_Node) have a LockCount ;specify "NoLockCount" in Class options if Class doesn't store the lock count if (!ClassObject) return Class := Class_getClass(ClassObject) if !RegExMatch(Class, "^(?\w+?(?<_Node>_Node)?)(?:\((?.*?)\))?$", Class) return false if (Class_Node) return false Loop, Parse, ClassOptions, `, , %A_Space%%A_Tab% { if (A_LoopField = "NoLockCount") return false } return true } */ /* Class_isClassNode(ClassObject) { if (!ClassObject) return Class := Class_getClass(ClassObject) return RegExMatch(Class, "^\w+_Node") } */ Class_setClass(ClassObject, ClassName) { if (!ClassObject) return ;stores the address to the ClassName for ; should be the return value from %ClassName%_initClass() NumPut(ClassName, ClassObject+0) } Class_getBuiltInSize(ClassObject) { if (!ClassObject) return return NumGet(ClassObject+4, 0, "uchar") } Class_setBuiltInSize(ClassObject, BuiltInSize) { if (!ClassObject) return NumPut(BuiltInSize, ClassObject+4, 0, "uchar") } Class_getUserDefinedSize(ClassObject) { if (!ClassObject) return UserDefinedSize := NumGet(ClassObject+5, 0, "uchar") ;0xFF = (uchar) -1 return (UserDefinedSize = 0xFF ? -1 : UserDefinedSize) } Class_setUserDefinedSize(ClassObject, UserDefinedSize) { if (!ClassObject) return NumPut(UserDefinedSize, ClassObject+5, 0, "uchar") } Class_getLockCount(ClassObject) { if !Class_hasLockCount(ClassObject) return return NumGet(ClassObject+6, 0, "ushort") } Class_decreaseLockCount(ClassObject) { if !Class_hasLockCount(ClassObject) return LockCount := NumGet(ClassObject+6, 0, "ushort") if (LockCount > 0) NumPut(LockCount - 1, ClassObject+6, 0, "ushort") } Class_increaseLockCount(ClassObject) { if !Class_hasLockCount(ClassObject) return LockCount := NumGet(ClassObject+6, 0, "ushort") ;0xFFFF = (UShort) -1 if (LockCount < 0xFFFF) NumPut(LockCount + 1, ClassObject+6, 0, "ushort") } ;for use with %ClassName%_Node objects /* Class_getNode(ClassObject) { if !Class_isClassNode(ClassObject) return return Class_getValue(ClassObject, "a4", "obj") } */ ;for use with %ClassName%_Node objects /* Class_getNodeAddress(ClassObject) { if !Class_isClassNode(ClassObject) return return NumGet(ClassObject+4) } */ ;for use with %ClassName%_Node objects /* Class_setNode(ClassObject, node) { if !Class_isClassNode(ClassObject) return return Class_setValue(ClassObject, "a4", node, "obj") } */ ;for use with %ClassName%_Node objects /* Class_replaceNode(ClassObject, node) { if !Class_isClassNode(ClassObject) return return Class_setValue(ClassObject, "a4", node, "robj") } */ ;for use with %ClassName%_Node objects ;only called from a destroy function /* Class_clearNode(ClassObject) { if !Class_isClassNode(ClassObject) return NumPut(0, ClassObject+4) } */ ;capacity is a replacement for Class_getUserDefinedSize if size is > 254 ;(size MUST be set to -1 via Class_setUserDefinedSize(ClassObject, -1) ;this takes up 4 bytes of memory (which must be specified in the built-in size) ;also, this value is set in "b1" - so "b1" cannot be used for a different value Class_getCapacity(ClassObject) { if (Class_getUserDefinedSize(ClassObject) != -1) return return NumGet(ClassObject+8, 0, "uint") } Class_setCapacity(ClassObject, Capacity) { if (Class_getUserDefinedSize(ClassObject) != -1) return NumPut(Capacity, ClassObject+8, 0, "uint") } /* Value getter and setters */ Class_getIndex(ThisIndex, ThisSize) { ;converts a one-based index to a valid index ;if ThisSize = 0, returns 0 ;if ThisIndex = 0, then ThisSize is returned ;if ThisIndex > ThisSize, then ThisSize is returned ;negative numbers wrap (e.g. -1 -> ThisSize - 1) ;if result <= 0 (after wrap), then 1 is returned ;else, ThisIndex is returned if (ThisSize = 0) return 0 else if (ThisIndex > 0) return (ThisIndex > ThisSize ? ThisSize : ThisIndex) else if (!ThisIndex) return ThisSize else if (ThisIndex < 0) { ThisIndex += ThisSize return ThisIndex <= 0 ? 1 : ThisIndex } } ;used by Class_get/setValue to parse the index value ;and convert to a zero-based byte offset. ;BitOffset stores the bit offset, if used, or "" if not specified Class_getOffset(ClassObject, index, ByRef BitOffset) { ;static ClassValues = 8 static OffSetForm = "^(?[a-z]*)(?\d++)(?[a-c])?(?:-(?\d++))?$" StringLower, index, index if !RegExMatch(index, OffsetForm, Offset) return BitOffset := OffsetBit if (OffsetByte) OffsetByte := Asc(OffsetByte) - Asc("a") + 1 else OffsetByte := 0 if (OffsetType = "") { ;-1 converts index from one-based to zero-based ;(+8 for the first 8 bytes reserved in each Class for "Class values") index := 4 * (OffsetWord - 1) + OffsetByte + Class_getBuiltInSize(ClassObject) + 8 } else if (OffsetType = "b") { ;-1 converts index from one-based to zero-based ;(+8 for the first 8 bytes reserved in each Class for "Class values") index := 4 * (OffsetWord - 1) + OffsetByte + 8 } else if (OffsetType = "w") { ;used by containers to access words ;-1 converts index from one-based to zero-based index := 4 * (OffsetWord - 1) } else if (OffsetType = "ww") { ;used by containers (using double word "nodes") to access the 1st value ;-1 converts index from one-based to zero-based index := 8 * (OffsetWord - 1) } else if (OffsetType = "wwa") { ;used by containers (using double word "nodes") to access the 2nd value ;-1 converts index from one-based to zero-based index := 8 * (OffsetWord - 1) + 4 } else if (OffsetType = "a") { index := OffsetWord } return index } ;facilitates using read-only (write-once) values ;ClassObject[index] is the bit-flag used to track if the value has been written yet Class_canWrite(ClassObject, index, type) { if Class_getValue(ClassObject, index, type) { ;value was already written once, cannot write again return false } ;set the bit-flag to specify that the value has been written to Class_setValue(ClassObject, index, true, type) ;value has not been written, can write it now return true } Class_getString(ClassObject, index) { return Class_getValue(ClassObject, index, "string") } Class_getSString(ClassObject, index) { return Class_getValue(ClassObject, index, "SString") } Class_getValue(ClassObject, index, Type = "uint") { ;static ClassValues = 8 ;get user-entered numeric values if (!ClassObject) return index := Class_getOffset(ClassObject, index, BitOffset) if (Type = "string" || Type = "SString") { return Class_toString(NumGet(ClassObject+index)) } else if (Type = "obj" || Type = "ptr" || Type = "sptr") { ;(does not modify LockCount) ThisClassObject := NumGet(ClassObject+index) if ClassName := Class_isWrapper(ThisClassObject) { ;unwrap value in wrapper if (ClassName = "String" || ClassName = "SString") return Class_toString(NumGet(ThisClassObject+8)) else if ClassName in Int,UInt,Float,Double,Int64,UInt64 { return NumGet(ThisClassObject+8, 0, ClassName) } else if ClassName in Short,UShort,Char,UChar return NumGet(ThisClassObject+4, 0, ClassName) else return %ClassName%_getValue(ThisClassObject) } return ThisClassObject } else { ;basic type if (BitOffset) { BitOffset := 1 << BitOffset - 1 return NumGet(ClassObject+index, 0, Type) & BitOffset } } ;Extract value return NumGet(ClassObject+index, 0, Type) } Class_setString(ClassObject, index, string) { return Class_setValue(ClassObject, index, string, "string") } Class_setSString(ClassObject, index, string) { return Class_setValue(ClassObject, index, string, "SString") } Class_setValue(ClassObject, index, value, Type = "uint") { ;set user-entered numeric values if (!ClassObject) return index := Class_getOffset(ClassObject, index, BitOffset) if (Type = "string" || Type = "SString") { ;Free previous string. if (addr := NumGet(ClassObject+index)) { oldValue := Class_toString(addr) Class_free(addr) ;Store NULL pointer to represent empty string when value = "", ;or in case error-handling is added and the function returns early. NumPut(0, ClassObject+index) } if (value = "") return oldValue ;Allocate buffer for string. newString := Class_Alloc("a" . strLen(value) + 1) ;Copy string into buffer. Class_CopyString(newString, value) ;Store pointer to string. NumPut(newString, ClassObject+index) return oldValue } else if (Type = "obj") ;must be used to keep lock count accurate { if (value = oldValue := NumGet(ClassObject+index)) return false if (oldValue) { ;store return to use as return to this function ReturnValue := Class_destroyThis(oldValue) if (ReturnValue = 0) { ;the object still exists, return the lock count (as a negative) ReturnValue := -Class_getLockCount(oldValue) } } if (value) Class_increaseLockCount(value) Type := "uint" } else if (Type = "robj") { if (value = oldValue := NumGet(ClassObject+index)) return false ReturnValue := oldValue if (oldValue) Class_decreaseLockCount(oldValue) if (value) Class_increaseLockCount(value) Type := "uint" } else if (Type = "ptr" || Type = "sptr") { ;pointer to an object - return the previous pointer ReturnValue := NumGet(ClassObject+index) Type := "uint" } else { ;basic type - return previous value ReturnValue := NumGet(ClassObject+index, 0, Type) if (BitOffset) { BitOffset := 1 << BitOffset - 1 ;don't modify value, only return present state if (value = "") return ReturnValue & BitOffset if (value) value := ReturnValue | BitOffset else value := ReturnValue & ~BitOffset ReturnValue &= BitOffset } } ;Store value NumPut(value, ClassObject+index, 0, Type) return ReturnValue }