Typed properties - experimental build available

Discuss the future of the AutoHotkey language
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

25 Aug 2023, 00:28

GetCursorPos(&mp1) ; Let the call construct a new struct.
MsgBox WinGetClass(WindowFromPoint(mp1))

GetCursorPos(mp2 := Point()) ; Output into an existing struct.
MsgBox WinGetClass(WindowFromPoint(mp2))

Out(sc) {
oc := Class()
oc.Prototype := op := {}
op.DefineProp 'p', {type: 'uptr'}
op.DefineProp '__value', {set: setvalue}
Could op.DefineProp 'p', {type: 'uptr'} be changed to op.DefineProp '__p', {type: 'uptr'}. .p is the built-in property name, but it might break the existing codes. At least for me, I sometimes expand a Classname by using Classname1.p currently. If I had already kept it in mind that p would become a built-in property name in the future, I dare not use p again. Property p really might have already been defined by user for his own purpose. Any way, using __p as a built-in property name seems much more reasonable than p.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

25 Aug 2023, 02:45

__value must be a property, not a method.

"VarRef doesn't have __value property" is not a valid reason. I think that you didn't think it through.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

25 Aug 2023, 11:32

lexikos wrote:
25 Aug 2023, 02:45
__value must be a property, not a method.

"VarRef doesn't have __value property" is not a valid reason. I think that you didn't think it through.
Oh, yes. 🤣I just forgot it a few days later. I have corrected it now.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

01 Sep 2023, 18:26

V2User wrote:
25 Aug 2023, 00:28
Could op.DefineProp 'p', {type: 'uptr'} be changed to op.DefineProp '__p', {type: 'uptr'}. .p is the built-in property name, but it might break the existing codes.
I didn't notice this post before as it was on a new page.

I don't know what you were imagining, but 'p' is clearly just a property I am defining in the example, on a user-defined object that is separate to any other objects that you might create in the script.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

02 Sep 2023, 06:10

Helgef wrote:
23 Jul 2023, 08:27
I don't see any benefit of the added flexibility, just added complexity, distracting from the primary purpose.
I am still interested to hear the answer to this:
lexikos wrote:
24 Jul 2023, 04:43
What complexity is it that you think was added, and why do you think it was added?
It wasn't rhetorical. Am I overlooking something?

You wrote about added complexity distracting from the purpose, and that simpler is better, but I have to think you had some specific issue in mind, as the generalisation doesn't make much sense. To me it seems that less flexibility in what the new features can do means more that the script will have to do for itself. The implementation will be simpler, and the scripts more complex. What I aimed for, and I think mostly achieved, is flexibility and simplicity. A few small additions mesh together with what objects can already do to allow the creation of classes that can take most of the fuss out of dealing with external functions and data types.

It is my intention to implement built-in classes (or functions that generate classes) for typed arrays, strings, pointers to structs and output parameters, but it's already clear that scripts can implement such things and any other abstract types, so I feel this experiment is a success. I can't see how a different path could have been simpler, even putting aside some of my goals.

my concern is more related to if we have to define size and ptr props for each struct, or write custom enums if want to traverse a class/struct's typed props only, or similar.
You should not need to define a size property as there should be no reason to get the struct's size. I don't think there's a good reason to allow implicitly passing a struct to NumGet/NumPut/StrGet/StrPut.

You do not need to define a ptr property for DllCall (anymore), but depending on the type, you may want to define one so that the "ptr" arg type passes some value other than the struct's address. BSTR, for example, can be passed to a "Ptr" parameter or StrGet, and the value of the struct (the BSTR pointer) will be used, not the struct's address.

I'm not sure why "traversing a class/struct's typed props only" would be a consideration. For general objects, there is only a single enumerator of own properties. This is utter simplicity, and creates complexity in scripts at the same time. If enumerating typed properties is a commonly-needed task, facilitating that is just a matter of adding an enumerator for that purpose, regardless of how everything else is implemented. If I had taken a different path where Struct is a new type derived from Any, not sharing implementation with Object, I suppose that the enumerator would have been specific to structs. Instead, there are some possibilities that can solve the more general problem of enumerating an object's (inherited and own) properties.



I was making an oversight when I wrote this:
lexikos wrote:
15 Jul 2023, 04:14
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.
After some more thinking, I concluded that it doesn't make sense for x : Map (for instance) to declare a nullable reference to a Map while x : POINT declares a nested struct, x : i32 declares an integer, and x : BSTR declares a BSTR (see example). The type expression should specify - or the type class should define - the type of the property, and to do that, it has to be instantiated.

Maybe C++ has biased my thinking.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

03 Sep 2023, 06:35

I merged this into v2.1-alpha.3, with the following changes:
  • Removed the N and "structN" DllCall arg types.
  • When unset is passed to a struct parameter, it is passed along to __value instead of throwing immediately.
  • ObjAllocData and ObjFreeData are removed (actually, disabled by preprocessing).
  • Fixed some bugs.
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: Typed properties - experimental build available

04 Sep 2023, 02:06

That's a very nice addition, thank you much, ↓ is indeed true and painful
This requires calculating the offset of each individual field as needed, so is a laborious and error-prone process.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

04 Sep 2023, 06:19

lexikos wrote:
01 Sep 2023, 18:26

I don't know what you were imagining, but 'p' is clearly just a property I am defining in the example, on a user-defined object that is separate to any other objects that you might create in the script.
Because I couldn't find from your code that when and where the propterty p will be read, as a output from dllcall. So, I assume that p would be read automatically by script when needing to get the struct data from the object. p seems some methods like __new() which will be used by script implicitly and automatically.
But now, I guess I have understood it. Maybe p is just a user property which can be defined with any name for an output.🤣
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

04 Sep 2023, 17:35

The struct is passed to DllCall, which passes the entire struct by value to the external function. It doesn't care what the property names are.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

05 Sep 2023, 04:14

lexikos wrote:
04 Sep 2023, 17:35
The struct is passed to DllCall, which passes the entire struct by value to the external function. It doesn't care what the property names are.

Code: Select all

class Point {
    x : i32, y : i32
}
WindowFromPoint := DllCall.Bind("WindowFromPoint", Point,, "uptr")
GetCursorPos := DllCall.Bind("GetCursorPos", Out(Point), unset)

GetCursorPos(&mp1) ; Let the call construct a new struct.
MsgBox WinGetClass(WindowFromPoint(mp1))

GetCursorPos(mp2 := Point()) ; Output into an existing struct.
MsgBox WinGetClass(WindowFromPoint(mp2))

Out(sc) {
    oc := Class()
    oc.Prototype := op := {}
    op.DefineProp 'p', {type: 'uptr'}
    op.DefineProp '__value', {set: setvalue}
    setvalue(this, value) {
        if value is VarRef
            this.p := ObjGetDataPtr(%value% := sc())
        else if value is sc
            this.p := ObjGetDataPtr(value)
        else
            throw TypeError('Expected a VarRef or ' sc.Prototype.__Class ' but got a ' type(value), -1)
    }
    return oc
}
So, after executing, the x,y of mp2 will be the cursor pos, however the x,y of mp1 will turn out to be blank. Is that right?
image.png
image.png (39.03 KiB) Viewed 3381 times
Last edited by V2User on 12 Sep 2023, 06:12, edited 1 time in total.
User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Typed properties - experimental build available

06 Sep 2023, 21:48

@lexikos
ObjSetDataPtr does not affect nested objects, as they each have their own data pointer (which points into the outer object's original data).
After setting the data pointer, if the data is allocated by the object, it will be released, so the original data pointer held by the nested structure will point to invalid memory. I recommend iterating over nested structures to update their data Pointers synchronously.
For a typed property, the return value of GetOwnPropDesc is a new object with the own properties Type and Offset.
Then can Desc of DefineProp(Name, Desc) add the optional Offset property to specify the custom offset?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

10 Sep 2023, 01:34

@thqby
That would make sense, but the function was never really intended for use (on a live struct, in particular).
Warning: This function is likely to be removed or replaced with a "pointer to struct" type, but is currently the only way to make direct use of typed properties with an externally allocated struct.
Allowing DefineProp to specify an offset seemed like the simplest way to support unions, so I already planned that. It's not as high priority as pointers to structs or arrays, though.

@V2User
They are not blank.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

10 Sep 2023, 06:17

lexikos wrote:
04 Sep 2023, 17:35
The struct is passed to DllCall, which passes the entire struct by value to the external function. It doesn't care what the property names are.
[Code1:]

Code: Select all

class Point {
    x : i32, y : i32
}
Thanks for your answer. ☕
Maybe I have understood more about struct now.
That is to say, the define order of x,y in Pointer cannot be changed, such as changing to [Code2]. Otherwise, it will go wrong. Is that right?
[Code2:]

Code: Select all

class Point {
     y : i32,x : i32
}
just me
Posts: 9466
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Typed properties - experimental build available

10 Sep 2023, 09:11

POINT structure

Code: Select all

typedef struct tagPOINT {
  LONG x;
  LONG y;
} POINT, *PPOINT, *NPPOINT, *LPPOINT;
If you pass class POINT to an API function expecting a POINT structure the order of the returned values is x, y. But of course you can choose the name y for x and vice versa if you have a reason to do it.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

11 Sep 2023, 23:20

just me wrote:
10 Sep 2023, 09:11
POINT structure

Code: Select all

typedef struct tagPOINT {
  LONG x;
  LONG y;
} POINT, *PPOINT, *NPPOINT, *LPPOINT;
If you pass class POINT to an API function expecting a POINT structure the order of the returned values is x, y. But of course you can choose the name y for x and vice versa if you have a reason to do it.
Thanks for your answer. :) I guess I have learned a big knowledge point about struct.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

13 Sep 2023, 08:05

@lexikos
Wish to support Type#X syntax to mark the structure member order.
The defining order of the members of structures are not important for C/C++ and other compiled languages, as they recognize the order by the members' names before compiling. However, the defining order is very important for any dynamic languages including AHK. Any changes to the order will go wrong. So this syntax is to solve the issue.
Just an example, maybe not so properly:

Code: Select all

class RectWidth {
	left:i32#1
	right:i32#3
}
class Rect extends RectWidth{
	top:i32#2
	bottom:i32#4
}
rr:=Rect()
DllCall('GetClientRect','ptr',WinExist('A'),Rect,rr)
After the first instance of Rect is created, the 4 struct members will be sorted in ascending order by i32#X. After that, the order cannot be changed, and #X cannot be added or overridden any longer.
Without this syntax, the struct order will be left,right,top,bottom. It's not right and will go wrong.
But with this syntax, the struct order will be right. Also, the order will be more robust and can be controlled more easily and flexibly. It has more expressiveness and brings more assurance for the correctness of the structure passed to DllCall.
Last edited by V2User on 14 Sep 2023, 10:19, edited 7 times in total.
just me
Posts: 9466
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Typed properties - experimental build available

13 Sep 2023, 08:24

@V2User, do you know any function accepting a RectWidth structure? :roll:

Your suggestion makes no sense to me. Filling arbitrary 'holes' in previously defined 'perforated' structures only confuses.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: Typed properties - experimental build available

13 Sep 2023, 09:28

just me wrote:
13 Sep 2023, 08:24
@V2User, do you know any function accepting a RectWidth structure? :roll:
Your suggestion makes no sense to me.
Maybe you are right. Perhaps because I am a beginner in WinAPI, I can't find a much proper example at this moment.

But I think in common, most methods and properties do fill the "holes" spared by the superclasses, acting like #X.
User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Typed properties - experimental build available

14 Mar 2024, 20:01

Code: Select all

MsgBox(ObjGetDataPtr(obj := Object()))  ; Nonzero value
@lexikos
Is it necessary for a class to allocate memory when it has no typed properties?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Typed properties - experimental build available

24 Mar 2024, 03:06

v2.1-alpha.9 (out now)
Rather than constructing a struct and then replacing the already-allocated pointer with ObjSetDataPtr (which will probably be removed), StructFromPtr can be used to create a "typed pointer". At the moment it is more or less the same as a struct instance, but with __delete disabled.

I'm not really sure how to handle type identity. For the moment, StructFromPtr(RECT, p) is RECT is true, by design. The pointer instance does not redirect get/set/call, but rather behaves as an instance, inheriting all defined properties from RECT.Prototype, which is its actual base object. I think it's probably fine.

However, some structs contain pointers to other structs (but I can't think of any easy examples). I think that there should be a built-in way for the script to specify that a DllCall parameter or property/struct field is a pointer-to-something, so getting it would do the equivalent of calling StructFromPtr(something, value). Setting it (or passing a value if used as a DllCall parameter type) would verify the type of struct being passed.

The following demonstrates some of the ideas I have in mind (with Struct hypothetically being built-in, but actually being defined below):

Code: Select all

#Requires AutoHotkey v2.1-alpha.9

class RECT extends Struct {
    left: i32, top: i32, right: i32, bottom: i32
}

NumPut('int', 1, 'int', 2, 'int', 3, 'int', 4, buf := Buffer(16, 0))

r := RECT.at(buf.ptr)

MsgBox "RECT.size = "       RECT.size
    . "`nr.size = "         r.size
    . "`nStruct.Size(r) = " Struct.Size(r)
    . "`nr = {" r.left "," r.top "," r.right "," r.bottom "}"

; r.foo := "bar" ; Error

; In lieu of an API that returns a struct pointer:
make_RECT := DllCall.Bind("msvcrt\malloc", "ptr", 16, RECT.Pointer)
free_RECT := DllCall.Bind("msvcrt\free", RECT.Pointer, )
GetWindowRect := DllCall.Bind("GetWindowRect", "ptr", , RECT.Pointer, )

r := make_RECT()
; GetWindowRect(WinExist("A"), buf := Buffer(16, 0)) ; Error (buf is not a RECT)
GetWindowRect(WinExist("A"), r)
MsgBox "r = {" r.left "," r.top "," r.right "," r.bottom "}"
free_RECT(r)



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),
        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
}
Typed pointers might be restricted to Struct-derived classes, with structs being restricted to typed or inherited properties only; no own properties. Otherwise, a typed pointer would provide a sort of limited view of an object, with methods and dynamic properties being unable to see or access own properties of the original object.

Once typed array support is added, that would be integrated into Struct; e.g. RECT[n] would return a class object usable in typed property definitions.

I consider implementing struct x and struct x extends y as simple syntax sugar equivalent to class x extends Struct or class x extends y (with y restricted to Struct subclasses).

Is anyone using typed properties in any interesting publicly-available scripts?

The feature set is certainly incomplete, but does anyone who has been putting these features to use have thoughts on what's needed in particular, or areas that are especially difficult?


@thqby no, that's been fixed.
Last edited by lexikos on 27 Mar 2024, 04:26, edited 1 time in total.
Reason: Fixed __value to return 0 for null pointers

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 72 guests