I have uploaded a build (not even officially an alpha/pre-release yet) to experiment with.The primary purpose is basically "struct support", but this would be more flexible than (just) having a dedicated struct type. Instances of a class with typed properties would have additional space allocated for them, coinciding with how C handles structs, so they can fill that role. The definition or redefinition of typed properties would not be permitted after creating the first instance. Aside from their use with external functions, classes utilizing typed properties could be more memory-efficient, more robust, and probably faster to initialize and delete.
Source: Potential priorities for v2.1 - AutoHotkey Community
AutoHotkey_2.1-alpha.1.1.zip (SHA256 hash)
AutoHotkey_2.1-alpha.1.2.zip (SHA256 hash) - 2023-07-21
AutoHotkey_2.1-alpha.2.1+ge0d87105.zip (SHA256 hash) - 2023-08-18
- Source code is on the ptype branch.
- Documentation not included.
- Syntax and behaviour may be substantially different in future releases.
Dynamic Properties
Properties are added to a prototype object with .DefineProp(Name, {Type: propType}).
Currently propType can be the name of a basic type, or a reference to a class (or object with a Prototype property) which has typed properties.
Typed Property Declarations
Properties can be declared in the class body with either name : propType or name : propType := initializer. I wasn't sure about this syntax, but couldn't think of anything better, and it sure is better than using static __new and DefineProp.
Currently propType can be the name of a basic type (without quotes), or the name of a previously-defined class which has typed properties.
Referring to a class which hasn't yet been defined might be possible in future.
GetOwnPropDesc
If the property is a typed property definition, the descriptor has two properties:
- Type contains the type name or class object.
- Offset contains the offset of the property, in bytes.
Basic Types
Currently only numeric types are implemented.
- Signed integer types are currently "i" followed by size in bits, e.g. i32.
- Unsigned integer types are currently "u" followed by size in bits, e.g. u32.
- Floating-point types are "f" followed by size in bits.
- There is no type name for pointer-sized integers yet.
Complex Types
The current implementation is focused on struct support, so properties declared as having an object type are assumed to be nested structs, with the nested struct's data being embedded directly in the outer struct.
- Each nested object is automatically constructed and __new() is called without parameters.
- Nested objects are constructed in order of definition, and destructed in reverse order, after the outer object (but before it is actually deleted).
- If a nested object defines the __value property, it is returned or invoked instead of returning the nested object itself. For instance, with a property declared as StructProp : StructClass, getting or setting this.StructProp invokes the non-static __value getter or setter defined in StructClass. This allows classes to be used to implement various types, such as different kinds of strings or smart pointers.
- A reference to any nested object is sufficient to keep the outer object alive, without the internal circular reference causing any issues.
- Currently only classes with typed properties can be used as property types; e.g. m : Map is not supported.
Own Properties
Typed properties currently aren't considered own properties, because the "property name: value" pair exists only in the prototype.
Object Internal Data
Currently structured data is allocated separately to the Object itself and held in a pointer field. Aside from being simple to implement, this was initially intended to allow a class with typed properties to wrap around an external struct pointer. Supposing that this pointer field will always exist, I thought to also allow the script to make use of it to store an arbitrary 32 or 64-bit value, avoiding the need for an additional memory allocation.
However, I will most likely change this. The memory allocation of the object itself could be expanded to include the structured data. The address of the structured data would be calculated from the object pointer and information in the prototype. This would avoid increasing the size of objects which lack typed data. For objects with typed properties, it would reduce memory fragmentation and improve memory locality. External struct pointers would instead be handled by a new pointer-to-struct metaclass.
Currently the following experimental functions exist:
- ObjAllocData(Obj, Size) allocates Size bytes and stores the address in the object's data pointer field.
- ObjFreeData(Obj) immediately frees data allocated by ObjAllocData (it would otherwise be freed when the object is deleted).
- ObjGetDataPtr(Obj) gets the object's data pointer (also valid if the object has typed properties).
- ObjGetDataSize(Obj) gets the size which was passed to ObjAllocData (if that wasn't called, it returns 0).
- ObjSetDataPtr(Obj, Ptr) sets the object's data pointer. The script may use ObjGetDataPtr to retrieve it. The value is not required to be a valid pointer, unless Obj has typed properties, in which case it had better point to a valid struct. ObjSetDataPtr does not affect nested objects, as they each have their own data pointer (which points into the outer object's original data).
Alignment
Typed properties should be aligned consistent with default MSVC packing. There is currently no way to override this.
Sizeof
There is no built-in function yet, but it can currently be implemented like this:
Code: Select all
sizeof(classobj) {
sizer := {}
sizer.DefineProp 'a', {type: classobj.HasProp('Prototype') ? classobj : {Prototype: classobj}}
sizer.DefineProp 'b', {type: 'i8'}
return sizer.GetOwnPropDesc('b').Offset
}
Restrictions
Some restrictions and safety checks are not yet implemented. (I might elaborate later.)
DllCall
An object must still implement the Ptr property to be passed to a DllCall Ptr parameter. Eventually DllCall (or a more efficient alternative which can compose or define functions at script startup) might take a class as a parameter type, in which case the class would control how the parameter is passed.