Typed properties - experimental build available
Re: Typed properties - experimental build available
Should the __value.get of the pointer class be removed, so that null pointers can be retrieved?
Re: Typed properties - experimental build available
@thqby literally just removing the getter would defeat half the point. A field or return value of type RECT.Pointer is supposed to return a pointer-to-RECT, which behaves as a RECT. The object which has the __value property is an implementation detail hidden from the script by __value; it does not behave as a RECT.
The script works by using StructFromPtr to create an actual RECT object with its data pointer set to a pre-existing address. Hypothetically, the built-in implementation would use the RECT.Pointer class object both to define the property type and to implement the RECT-like object.
I intended for __value to return 0 for a null pointer, but forgot.
The script works by using StructFromPtr to create an actual RECT object with its data pointer set to a pre-existing address. Hypothetically, the built-in implementation would use the RECT.Pointer class object both to define the property type and to implement the RECT-like object.
I intended for __value to return 0 for a null pointer, but forgot.
Re: Typed properties - experimental build available
If __value returns 0 for a null pointer, there will be ambiguity when the type is int*.
The current solution also makes it impossible for pointer types to be used as arrays, like p[0], p[1].
The current solution also makes it impossible for pointer types to be used as arrays, like p[0], p[1].
Re: Typed properties - experimental build available
What's the best solution for a one-off structure returned by a DllCall? I can't define a class inside a function, but would like to wrap the pointer anyways. Also it's possible that a rectangle may be [xywh] or [xyx2y2] so a global definition may not be ideal.
Re: Typed properties - experimental build available
Code: Select all
fn() {
static RECT := () {
obj := Class(Object)
for k in ['x', 'y', 'w', 'h']
obj.Prototype.DefineProp(k, { type: 'i32' })
return obj
}()
DllCall(..., RECT, ...)
}
Re: Typed properties - experimental build available
A property of type int* would not return the int value; it would return an int*. How would you get the pointer value if it was automatically dereferenced?
Don't be confused by the fact that a RECT.Pointer property in the pre-release mockup implementation I posted returns a RECT; as I said, "a field or return value of type RECT.Pointer is supposed to return a pointer-to-RECT, which behaves as a RECT." Even if it appears to be a normal struct, the object encapsulates the pointer value, which can be retrieved with the Ptr property.
Assigning to a pointer property stores a new pointer; it does not dererence the existing pointer or new pointer value. If the property was int* rather than RECT*, it would still be for retrieving and setting pointer values, not int values. int* and int& can be distinct types if we ever want the pointer to be automatically dereferenced, returning and setting the int value instead of the pointer.
A single class object couldn't both replicate the behaviour of DllCall's int* return type and allow the pointer value to be retrieved from a struct, since __value's getter would need to have two different behaviours. But we can easily use multiple classes (or just not use a class for one or both purposes).
What do you mean by "current solution" or "impossible"? What would you have in mind instead?The current solution also makes it impossible for pointer types to be used as arrays, like p[0], p[1].
You can just add an __Item property to the prototype of the class you pass to StructFromPtr (explicitly or via class definition). For now, you also have the option of adding properties to the object returned by StructFromPtr.
Perhaps you are thinking that because a pointer object derives from the same base as an instance of Struct, it can't have an array indexer. But why can't a Struct have an array indexer? It has a Ptr property. Anyway, I don't necessarily intend for pointer objects and struct instances to be the same in the final release. As I said, "I'm not really sure how to handle type identity." That would imply that pointers and struct instances might be different types. Different types can have different properties. Also, "Support for pointers to structs is rudimentary." "There is currently no built-in way to specify a "pointer to struct" type."
The main point of the mockup I posted was just what the Struct class might provide in terms of syntax and reusable functionality for structs. It was hastily written based on whatever ideas I had in my head at the time.
Re: Typed properties - experimental build available
Yes, it can be done that way.
The first option is more convenient to use, but the second option is more reasonable and can distinguish their types.
In addition, it is convenient if the value of the structure can be set from an array or from an object key-value pair.
Code: Select all
#Requires AutoHotkey v2.1-alpha.9
class RECT extends Struct {
left: i32, top: i32, right: i32, bottom: i32
}
PRECT1 := PointerTo(RECT)
PRECT2 := PointerTo2(RECT)
r1 := DllCall('msvcrt\malloc', 'uptr', 32, PRECT1)
r2 := DllCall('msvcrt\malloc', 'uptr', 32, PRECT2)
DllCall("GetWindowRect", "ptr", WinExist("A"), PRECT1, r1)
DllCall("GetWindowRect", "ptr", WinExist("A"), PRECT2, r2)
MsgBox "r1 = {" r1.left "," r1.top "," r1.right "," r1.bottom "}, type = " Type(r1) "`n"
. "r2 = {" r2[0].left "," r2[0].top "," r2[0].right "," r2[0].bottom "}, type = " Type(r2) ""
r11 := r1[1]
r21 := r2[1]
DllCall("msvcrt\free", PRECT1, r1)
DllCall("msvcrt\free", PRECT2, r2)
class Struct {
static __new() {
if this != Struct
Throw
this.Prototype.DefineProp('Ptr', { get: ObjGetDataPtr })
this.Prototype.DefineProp('Size', { get: ObjGetDataSize })
this.DeleteProp('__new')
this.DefineProp('at', { call: StructFromPtr })
}
static Size => this.Prototype.Size
static Size(obj) => ObjGetDataSize(obj.Prototype ?? obj)
static Pointer => (this.DefineProp('Pointer', { value: c := PointerTo(this) }), c)
__Set(name, args, value) {
if args.Length
return this.%name%[args*]
Throw PropertyError('This value of type "' Type(this) '" has no property named "' name '".', -1)
}
}
PointerTo(structClass) {
static cache := Map()
if c := cache[structClass] ?? false
return c
if !ObjGetDataSize(structClass.Prototype)
Throw TypeError("Invalid class", -1)
c := Class(Object)
c.Prototype.cls := structClass
c.Prototype.DefineProp('Ptr', { type: 'uptr' })
c.Prototype.DefineProp('__value', {
get: (this) => this.ptr && StructFromPtr(this.cls, this.ptr).DefineProp('__item', {
get: (this, index) {
if !index
return this
obj := { base: this.base }
ObjSetDataPtr(obj, this.ptr + index * ObjGetDataSize(this))
return obj
}
}),
set: (this, value) {
if value is Integer
this.ptr := value
else if value is this.cls
this.ptr := value.ptr
else
Throw TypeError("Expected a " this.cls.Prototype.__class " or Integer; received a " Type(value), -1)
}
})
cache[structClass] := c
return c
}
PointerTo2(structClass) {
static cache := Map()
if c := cache[structClass] ?? false
return c
if !ObjGetDataSize(structClass.Prototype)
Throw TypeError("Invalid class", -1)
c := Class(structClass.Prototype.__class '*', Object)
c.Prototype.cls := structClass
c.Prototype.DefineProp('Ptr', { type: 'uptr' })
c.Prototype.DefineProp('__value', {
set: (this, value) {
if value is Integer
this.ptr := value
else if this.base == value.base || value is this.cls
this.ptr := value.ptr
else
Throw TypeError("Expected a " this.__class " or " this.cls.Prototype.__class " or Integer; received a " Type(value), -1)
}
})
c.Prototype.DefineProp('__item', {
get: (this, index) {
if !ptr := this.ptr
Throw ValueError("null pointer")
if index
ptr += index * this.cls.size
return StructFromPtr(this.cls, ptr)
}
})
cache[structClass] := c
return c
}
Re: Typed properties - experimental build available
@thqby stupendous code, will be very useful.
Re: Typed properties - experimental build available
@thqby
If the pointer type is used as a property of a struct, a PointerTo-based property will return an object which represents the pointer value. The actual pointer value is copied into the object's internal data pointer field. Modifying the original struct will have no effect on a previously-retrieved pointer value. By contrast, a PointerTo2-based property will return the struct which represents the property itself, which behaves more like a reference to a pointer variable. If the original struct is modified, a previously-retrieved reference to the pointer will use the new pointer value, not the one it had when the reference was retrieved. Mutating the PointerTo2 object will affect the struct as well, but that is a lesser concern.
As for type identity, if R returned from a RECT.Pointer property has all of the properties of a RECT, should R is RECT be true or false? If false:
On the other hand, we can also just say that a RECT.Pointer property returns a reference to a RECT (perhaps the name RECT.UnsafeRef would be more apt for that concept).
To prevent an object created by StructFromPtr from executing __Delete, currently an internal flag is set on the object. This could be more directly exposed to allow the script to determine whether __Delete will be called, and to suppress __Delete for other objects. .NET has something similar: GC.SuppressFinalize(this);
If the pointer type is used as a property of a struct, a PointerTo-based property will return an object which represents the pointer value. The actual pointer value is copied into the object's internal data pointer field. Modifying the original struct will have no effect on a previously-retrieved pointer value. By contrast, a PointerTo2-based property will return the struct which represents the property itself, which behaves more like a reference to a pointer variable. If the original struct is modified, a previously-retrieved reference to the pointer will use the new pointer value, not the one it had when the reference was retrieved. Mutating the PointerTo2 object will affect the struct as well, but that is a lesser concern.
As for type identity, if R returned from a RECT.Pointer property has all of the properties of a RECT, should R is RECT be true or false? If false:
- Presumably R is RECT.Pointer should be true.
- Hypothetically there should be a Pointer base class, so R is Pointer is true.
- There should be some way to determine the Class (not just type name) of struct it points to.
(I think that's .cls in your implementation, but I assume that's an implementation detail.)
- When the smart pointer is no longer referenced, it executes __Delete and deletes the struct.
- Although both types of pointer objects can (at the moment) have own properties, the smart pointer being the "original" and potentially nested in some other object makes a difference to whether an author might decide to make use of that.
On the other hand, we can also just say that a RECT.Pointer property returns a reference to a RECT (perhaps the name RECT.UnsafeRef would be more apt for that concept).
To prevent an object created by StructFromPtr from executing __Delete, currently an internal flag is set on the object. This could be more directly exposed to allow the script to determine whether __Delete will be called, and to suppress __Delete for other objects. .NET has something similar: GC.SuppressFinalize(this);