Preview of changes: scope, function and variable references

Discuss the future of the AutoHotkey language
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

05 Mar 2021, 16:55

sirksel wrote:
05 Mar 2021, 11:47
This doesn't mean you have to (or will have to in the foreseeable future) re-declare a global in a either outer or successive levels of inner functions to have it available for reading by the innermost functions.
I was only talking about local variables. Global and static variables are merely referenced, not captured; that is something unique to closures.

Reading a uniquely-named global variable does not require declaring it, let alone re-declaring it. That's the point of "assign-local".

From the new documentation:
Nested Functions
...
By default, a nested function may access any static variable of any function which encloses it, even dynamically.

By default, a nested function automatically "captures" a non-static local variable of an outer function when the following requirements are met:
  • The outer function must refer to the variable in at least one of the following ways:
    • By declaring it with local.
    • By declaring it in the parameter list.
    • As the non-dynamic target of an assignment or the reference operator (&).
  • The inner function (or a function nested inside it) must refer to the variable non-dynamically.
A nested function which has captured variables is known as a closure.

Non-static local variables of the outer function cannot be accessed dynamically unless they have been captured.

Explicit declarations always take precedence over local variables of the function which encloses them. For example, local x declares a variable local to the current function, independent of any x in the outer function. Global declarations in the outer function also affect nested functions, except where overridden by an explicit declaration.

If a function is declared assume-global, any local or static variables created outside that function are not directly accessible to the function itself or any of its nested functions. By contrast, a nested function which is assume-static can still refer to variables from the outer function.

Functions are assume-local by default, and this is true even for nested functions, even those inside an assume-static function. However, if the outer function is assume-global, nested functions behave as though assume-global by default, except that they can refer to local and static variables of the outer function.

Each function definition creates a read-only variable containing the function itself; that is, a Func or Closure object. See below for examples of how this might be used.
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Preview of changes: scope, function and variable references

05 Mar 2021, 18:00

Thanks, @lexikos! This makes perfect sense. I like how you covered the special cases in the docs. Really gives a complete understanding of what is going on. I should have paid more attention to your word captured. I've noticed over the years how your words are always very carefully chosen. Thanks again for the follow-ups!
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

07 Mar 2021, 01:33

v2.0-a128 includes the changes from this branch, plus some additional changes and fixes. The documentation has also been updated.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Preview of changes: scope, function and variable references

07 Mar 2021, 10:12

Great job :thumbsup:

These changes looks like great simplifications and increased conveniences.

Cheers.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Preview of changes: scope, function and variable references

07 Mar 2021, 10:44

Why does isSet require a VarRef now?
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

07 Mar 2021, 23:05

Because
1) Otherwise, you would be reading the variable to pass its value to the function, and that would/should cause an error.
2) It simplifies the rules for when the VarUnset warning is shown. (I think I initially designed it so passing the variable to IsSet suppressed the warning for that variable, so one #include/segment can be designed to use a global variable which is optionally set by another #include/segment.)
3) All functions which deal with a variable rather than its value should take a VarRef. IsSet is a function, and can be shadowed by a local variable or user defined function.
4) It allows a VarRef contained by a non-ByRef parameter to be passed directly to IsSet without performing a double deref (and avoiding accidentally checking the parameter itself, which would yield true when it contains a VarRef even if the VarRef's target is unset).
iseahound
Posts: 1445
Joined: 13 Aug 2016, 21:04
Contact:

Re: Preview of changes: scope, function and variable references

08 Mar 2021, 13:44

Is there a reason to not implicitly dereference all VarRef parameters, when passed through functions, such as in:

doc

Code: Select all

Swap(Left, Right)
{
    temp := %Left%
    %Left% := %Right%
    %Right% := temp
}
to avoid percent signs, and keep the idea that &var is a VarRef?
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Preview of changes: scope, function and variable references

09 Mar 2021, 00:12

@lexikos, can we rely on the behavior that getters and methods and value props containing functions do not require the () or [] when they are called, so long as there are no mandatory params? For example:

Code: Select all

class c {
  static svar := 'class'
  static sget[x:=3] => msgbox(x * 10)
  static smeth(x:='dude') => msgbox('bye ' x)
  static sfunc := (t, x:='abc') => msgbox(strupper(x))
  ivar := 'instance'
  ifunc := (t, x:='Abc') => msgbox(strlower(x))
  iget[x:=5] => msgbox(x * 100)
  imeth(x:='dude') => msgbox('hi ' x)
}
o := c()
c.sfunc
c.smeth
(c.sget . '')
o.ifunc
o.imeth
(o.iget . '')
Don't get me wrong. I absolutely LOVE this, as it is ridding my code of tons of unnecessary ()... Since I'm changing thousands of lines to conform, I just wanted to be sure I could reasonably rely on the behavior, and that it wasn't just a fluke. Now AHK syntax and functional programming is like the best of JS, Python, Ruby, et al. So great! Thanks.
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

09 Mar 2021, 05:16

@iseahound
:headwall:

If you don't want percent signs, use a &ByRef parameter.


@sirksel
A function call statement is the only context where function and method calls do not require parentheses.

x.y[] - specifically without parameters - is precisely equivalent to (x.y)[]. First x.y is evaluated without parameters, then result[], which Object handles by invoking result.__Item and ComObject handles by invoking DISPID_VALUE.
Note: When an explicit property name is combined with empty brackets, as in obj.prop[], it is handled as two separate operations: first retrieve obj.prop, then invoke the default property of the result. This is part of the language syntax, so is not dependent on the object.
Source: Objects - Definition & Usage | AutoHotkey v2
object[params] is equivalent to object.__Item[params] when there are parameters.
object[] is equivalent to object.__Item.
Source: Objects - Definition & Usage | AutoHotkey v2
In other words, (o.iget) is the right way to invoke the property with zero parameters.
For example, obj.Property would call get while obj.Property := value would call set.
Source: Objects - Definition & Usage | AutoHotkey v2
iseahound
Posts: 1445
Joined: 13 Aug 2016, 21:04
Contact:

Re: Preview of changes: scope, function and variable references

09 Mar 2021, 11:15

There doesn't seem to be a reason to receive a VarRef object in the body of the function, given that it has no methods or properties, and the only subsequent action would be to check obj is VarRef. Given that there is only one thing to do to a VarRef object, is there a reason to not always dereference it? The benefit would be to reduce boilerplate, and there doesn't seem to be downsides. (I am aware of &ByRef parameter, and I am only speaking about the general case.)
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

09 Mar 2021, 17:15

There are many other things to do with any value, even without knowing what it is:
  • Assign it to another variable.
  • Put it in an object or array.
  • Return it from the function.
  • Pass it along to another function.
  • Use it as key in a lookup.
  • ... more? I'm not going to limit the possibilities.
There is only one "benefit" to automatically dereferencing the parameter:
  • Allow percent signs to be omitted when a VarRef is expected but not required, saving a couple of characters.
There are at least these drawbacks:
  • Reduction in flexibility.
  • Reduction in clarity.
  • No way to check whether the parameter is an alias for another variable, as param is VarRef would dereference the VarRef and return its content. IsByRef could maybe be re-implemented with some difficulty (see OP), but as is we have a perfectly good way to check the type of a value.
  • Performance cost of dynamically checking the type of any object whenever a variable (or at least parameter) is read or assigned, if that value contains an object; or at the very least, checking the type of each object parameter value when the function is called.
  • &param would then only enforce VarRef as a requirement rather than change the meaning of the parameter, which doesn't make a lot of sense.
  • If it applies only to parameters, inconsistency between parameters assigned a VarRef via function calling and variables assigned a VarRef through assignment.
  • If the mechanism works by what value the parameter contains when it is referenced: once assigned a VarRef there would be no way to unassign it, which is especially bad if it applies to all variables. Assigning a value returned by a function call, property or array element then becomes suspect because it could turn the variable into an alias for another variable.
  • It's not clear what should happen when a variable which is already an alias is assigned a VarRef, or another variable which is an alias.
It would defeat the second point of the ByRef changes, which was to turn a "variable reference" into an ordinary value, and as such automatically add flexibility. (The first point being to make the passing of a reference explicit at the caller's side.)

The general case when not defining a parameter ByRef is that the function does not want to assign to the caller's variable, is not designed to do so, and should not do so even if the caller happens to pass a VarRef.


Please, no more ideas from you.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Preview of changes: scope, function and variable references

11 Mar 2021, 13:35

@lexikos Was GetMethod also supposed to be removed when properties and methods were merged in a128?
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

11 Mar 2021, 22:27

No. I even updated the documentation for it. It shares code with HasMethod.
User avatar
vvhitevvizard
Posts: 454
Joined: 25 Nov 2018, 10:15
Location: Russia

Re: Preview of changes: scope, function and variable references

13 Mar 2021, 03:06

@lexikos 1. HUGE LEAP FORWARD! Further improvement to get rid of AHK quirks! The first update I liked pretty much everything about it. On paper. I gotta convert my code base from v127 to v128+ and test out every aspect yet.
2. How many people asked for it! New VarRef operator saves lots of space (6 symbols ByRef_ to 1 &). It does help to memorize OutVar positions in the parameters list for built-in functions and gives an additional level of error checking control over it.
3. Super-global classes removal forces better programming practices and makes 1 step closer to better namespace management of Python-like libraries modularity.
4. Less redundant stuff (no more .new explicit method) to call constructors! No more local directives madness (sometimes it added up to 20% to code size for small functions).
5. Finally function's call/function's referencing syntax is similar to other languages! Though I would like one day u make function's call right parenthesis () mandatory and completely remove "command syntax". We've moved far away from AHK v1 anyways - no need to cling to this any longer: according to polls the majority is for command syntax removal.
6. Merged methods and properties namespace is now similar to other languages. Also now it lets check a class's methods and properties in one pass as properties. ;)
7. Also I noticed the documentation was greatly revamped with adding side-effects, known limitations and additional examples. Quality leap forward! Though the differences between the latest version and AHK v1, previous AHK v2 versions might require additional examples still. U might even want to create the whole "how to convert older scripts to AHK 2 v129" section.

Some issues and suggestions so far:
1.
Fat arrow functions in global scope are no longer assume-global. Due to the new rules, they can read global variables and create local variables, but not assign global variables.
At first I was super agitated to learn about v128+ on paper. But it turns out fat-arrow functions at the global scope r not assume-local (yet):

Code: Select all

global v:=1
f()=>v:=8 ;looks like it assigns to a global var still.

msgbox(A_AhkVersion) ;2.0 a129
msgbox("v: " v) ;1
f()
msgbox("v: " v) ;8

2.
Within an assume-local function, if a given name is not used in a declaration or as the target of a non-dynamic assignment or the reference (&) operator, it may resolve to an existing global variable.
Its not clear for me what do u mean by saying that reference & operator changing the way how AHK resolves global variables. Could u provide an example? Is it exactly the following side-effect (uncomment 1 line to make log local by assessing it with isset(&log):

Code: Select all

class log{
}

class ini{
	static load(){
	msgbox("inside class: isset(log): " isset(&log)) ;FALSE. really? isn't it declared as class (=initialized global var)?
	}
}

	msgbox("outside: isset(log): " isset(&log)) ;TRUE. ok
	ini.load()

class json{
	static dump(){
;		msgbox("inside class 2: isset(log): " isset(&log)) ;uncomment this to make log local
		msgbox(type(log)) ;class (ok) OR (if u uncomment the line above) "this var has not been assigned a value. specifically: local log (same as a global)"
	}
}

json.dump()
But how can I assess whether a class is declared outside or not w/o throwing errors and other side-effects of turning global to local resolution? before v128 I used isset(class_name) construction.

BTW, for debugging purpose it would be nice to have a special command to learn what AHK thinks of the current var within the current namespace scope (attributes such as global, local, static, simple nested function vs closure or any other (in)valid status). Stat(&var) or something like that that returns a string or an object describing its status (in details).
3.
DllCall: The output value (if any) of numeric parameters with the * or P suffix is ignored if the script passes a plain variable containing a number. To receive the output value, pass a VarRef such as &myVar or an object with a Ptr property.
* or P suffix remains for compatibility reasons but AHK doesn't let me use "int", &var for dllcall. It requires redundant "intP" in this case still.
4.
Each function definition creates a constant (read-only variable) within the current scope; therefore, scripts cannot assign to a variable with that name in the same scope.
This article in the documentation needs to be updated with heads up concerning methods. Methods (essentially, properties now) can be overwritten still thus converting an existing code having the same method and property name in a class from <128 versions flags no errors/warnings at load-time and may create unexpected results:

Code: Select all

class test{
	static start()=>test.f:=0 ;no errors here
	static F()=>msgbox("OK")
}
test.start()
test.f() ;yields an error at run-time thus leaving a sleeping hard-to-find error for rarely called functions.

I might further update on the matter in my wishlist topic.
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

13 Mar 2021, 18:31

vvhitevvizard wrote:
13 Mar 2021, 03:06
1. [...] But it turns out fat-arrow functions at the global scope r not assume-local (yet):
v should be local in that case. I'll look into it.
2. Its not clear for me what do u mean by saying that reference & operator changing the way how AHK resolves global variables.
The text you quoted is from the announcement topic, so is a little vague, though not nearly as vague as how you say it. If you want clarification, read the documentation.
But how can I assess whether a class is declared outside or not
Declare it. But you can't yet. ;) There used to be an exception for global x inside functions to permit redeclaration of globals, so you could import classes into force-local functions. But now classes aren't super-global and there are no force-local functions.

You can use assume-global. You can put it in a nested function to reduce risk.

Code: Select all

class ini {
	static load() {
		has_log() {
			global
			return IsSet(&log)
		}
		msgbox("inside class: isset(log): " has_log())
	}
}
This will allow you to see that the global Log variable is in fact set. Now, maybe you will rename your class so it doesn't conflict with a predefined function? Otherwise, you could have just used Log is Class.
3. * or P suffix remains for compatibility reasons but AHK doesn't let me use "int", &var for dllcall. It requires redundant "intP" in this case still.
I think you are greatly misunderstanding something.

The suffix has nothing to do with compatibility with other AutoHotkey versions, and is in no way redundant. "int", 0 passes 0 and "intP", 0 passes the address of a temporary stack variable containing 0. There's no reason to pass a VarRef to an "int" parameter, and it was never wise to pass the address of a variable to an "int" parameter. I think you probably meant "ptr", StrPtr(var), but maybe it was "ptr", ObjPtr(var). If you were passing a structure or buffer for something other than a string, you should be using BufferAlloc and not & or StrPtr.
4. This article in the documentation needs to be updated with heads up concerning methods. Methods (essentially, properties now) can be overwritten ...
This article? What article?
converting an existing code having the same method and property name in a class from <128 versions flags no errors/warnings at load-time and may create unexpected results
Unlike the announcement topic, the documentation is not concerned with previous v2 alpha versions. Methods were properties in the first place, in v1 and prior to (iirc) v2.0-a104.

I'm aware the documentation can use improvement, but I won't expound on that at the moment.
User avatar
vvhitevvizard
Posts: 454
Joined: 25 Nov 2018, 10:15
Location: Russia

Re: Preview of changes: scope, function and variable references

14 Mar 2021, 00:54

@lexikos thx for quick reply! :)

2.
Declare it. But you can't yet. ;)
If I try to re-declare a class declared somewhere else it yields a load-time error "this class declaration conflicts with an existing Class" that obviously cannot be suppressed with a try block.

Idea is for library functions to call some outer class's methods only if they r "enabled" (the corresponding methods/functions #included in the script), e.g.:

Code: Select all

;somewhere in included library's function/method:
isset(&logger) && logger.print("blahblahblah") ;logger class here is not mandatory
Another approach would be to declare a class with a dummy method(s) if a class is not declared:

Code: Select all

;somewhere in auto-execute code we figure out that logger class is missing so we dynamically create a class's dummy method(s):
isset(&logger) || logger:={print:(*)=>} ;IMPOSSIBLE ATM. lead to "error: this Class cannot be assigned a value"
...
;somewhere in included library's function/method:
logger.print("blahblahblah") ;now its unconditional. we just call it knowing it's either a full method or a dummy one doing nothing.
But having pre-processor's #ifdefined directive to conditionally opt between the current script lines (effectively, having several class declarations and other language constructions not possible at load-time and run-time) depending on the situation would be much better.
At the moment having several #includes of the same file/same class declarations leads to a load-time error with no way around it. That impedes script libraries development. Ideally it should be:

Code: Select all

#ifnotdefined logger ;pre-processor directive
#include <logger> ;pre-processor directive
#endif ;pre-processor directive
You can use assume-global. You can put it in a nested function to reduce risk.
ur code snippet does work, thank u! The kludge would help to convert old version scripts to new ones w/o rewriting the logic at least. :)
Now, maybe you will rename your class so it doesn't conflict with a predefined function?
:D The class name in a library happened long time ago and I was reluctant to change its name all over my depending on it scripts while it worked as expected... I've renamed it now. v128+ considers functions as read only vars now thus assignment statements like log:={print:(*)=>} r not possible: "Error: This Func cannot be used as an output variable". More strict rules encourage better programming practices. :D
Otherwise, you could have just used Log is Class.
logger is class construction looks nice but in case logger class is not declared it generates a warning "the var appears to never be assigned a value" at load-time AND an error "the var has not been assigned a value" at run-time. So its not convenient and essentially not proper for polling unset params/undeclared vars.
(...) If you want clarification, read the documentation.
Could u elaborate on this:

Code: Select all

msgbox(isset(&logger)) ;uncomment this to make log local
msgbox(type(logger)) ;class (ok) OR (if u uncomment the line above) "this var has not been assigned a value. specifically: local log (same as a global)"
Its beyond my grasp. I tried to search thru new documentation to no effect. I fail to see why isset(&logger) statement resolves logger as local for the subsequent operations? the 2nd line behaves as if isset declared a local uninitialized variable.
Last edited by vvhitevvizard on 15 Mar 2021, 03:34, edited 7 times in total.
User avatar
vvhitevvizard
Posts: 454
Joined: 25 Nov 2018, 10:15
Location: Russia

Re: Preview of changes: scope, function and variable references

14 Mar 2021, 02:56

3.
I think you are greatly misunderstanding something.

The suffix has nothing to do with compatibility with other AutoHotkey versions, and is in no way redundant. "int", 0 passes 0 and "intP", 0 passes the address of a temporary stack variable containing 0. There's no reason to pass a VarRef to an "int" parameter, and it was never wise to pass the address of a variable to an "int" parameter.
I suppose I'm fine with low-level programming at assembler level :D , I simply supposed that following VarRef "argument" makes preceding "type" string's P or * suffix redundant. But u r right - there is a case where we need neither output nor any AHK variable initialized and just wanna create a temporary variable to hold an immediate value to be passed as a pointer as with "intP", 0...

4.
What article?
E.g. this one concerning functions:
https://lexikos.github.io/v2/docs/Functions.htm
Quite comprehensive article but it actually lacks a subsection concerning functions declaration completely. And along with adding the v128 announcement statement (I consider it a crucial one):
Function and variable names are now placed in a shared namespace.
Each function definition creates a constant (read-only variable) within the current scope; therefore, scripts cannot assign to a variable with that name in the same scope.
something concerning "unlike functions, its possible to assign to a method with that name in the same class" should be added, too. Mentioning that some previous AHK v2 versions had separate property and method namespaces and thus some scripts might have conflicting property and method names that doesn't trigger an error in v128+ yet fails.

Yeah I recall all the previous class's reincarnations since ~2011 :)
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

14 Mar 2021, 23:30

lexikos wrote:But you can't yet. ;)
vvhitevvizard wrote:
14 Mar 2021, 00:54
If I try to re-declare a class declared somewhere else it yields a load-time error
See, I told you so.
Another approach would be to declare a class with a dummy method(s) if a class is not declared:
The assignment would need to be dynamic to avoid a load-time error; i.e. %'logger'% :=.

I think proper module/namespace support might supersede any solutions you come up with.

But having pre-processor's #ifdefined directive to conditionally opt between the current script lines [...] depending on the situation would be much better.
If what is defined? Unless it is applied retroactively after parsing all lines, it would have to be positional, whereas a class could be defined anywhere in any #include, before or after that directive.

At the moment having several #includes of the same file/same class declarations leads to a load-time error with no way around it.
Having the same function or class names defined in different files (or the same file accessed through different paths, such as with symbolic links) causes an error. #include only includes each unique path once, even if you use it multiple times; it is not the problem. I can't say that I've found the current rules regarding duplicate declarations to be a problem.

That impedes script libraries development.
I haven't experienced this myself, and can't say I agree.

v128+ considers functions as read only vars now thus assignment statements like log:={print:(*)=>} r not possible
Right, but %'log'%:={} would not cause a load-time error.

vvhitevvizard wrote:
lexikos wrote:Otherwise, you could have just used Log is Class.
logger is class construction looks nice but in case logger class is not declared it generates a warning
I think you missed the point of "Otherwise". You could have used Log is Class because Log is guaranteed to be defined.

I fail to see why isset(&logger) statement resolves logger as local for the subsequent operations? the 2nd line behaves as if isset declared a local uninitialized variable.
I think the documentation is very clear about it. You are just failing to see that isset(&logger) utilizes the reference operator.
Functions are assume-local by default. Variables accessed or created inside an assume-local function are local by default, with the following exceptions:
  • Global variables which are only read by the function, not assigned or used with the reference operator (&).
  • Nested functions may refer to local and static variables created by an enclosing function.
source: https://lexikos.github.io/v2/docs/Functions.htm#Local

Any variable reference in an assume-local function may resolve to a global variable if it is only read. However, if a variable is used in an assignment or with the reference operator (&), it is automatically local by default.
source: https://lexikos.github.io/v2/docs/Functions.htm#Global
I simply supposed that following VarRef "argument" makes preceding "type" string's P or * suffix redundant.
They are not. Clarity is important with DllCall, and it isn't necessarily the case that any VarRef received by DllCall was passed directly. It is also meaningful to pass a VarRef to a Str parameter, and there is a big difference between Str and Str*.
lexikos
Posts: 9589
Joined: 30 Sep 2013, 04:07
Contact:

Re: Preview of changes: scope, function and variable references

15 Mar 2021, 00:56

kczx3 wrote:
07 Mar 2021, 10:44
Why does isSet require a VarRef now?
lexikos wrote:
07 Mar 2021, 23:05
2) It simplifies the rules for when the VarUnset warning is shown. (I think I initially designed it so passing the variable to IsSet suppressed the warning for that variable, so one #include/segment can be designed to use a global variable which is optionally set by another #include/segment.)
I just remembered an important detail. Previously IsSet(var) would mark var as "assigned somewhere" to suppress the warning. However, with the implementation of the new scope rules that would mean IsSet(var) acts like &var in that it makes var local by default, because it's "assigned" in the function. IsSet(&var) has the same implication, but because of the & operator, not IsSet itself. I will think about alternative solutions.
User avatar
vvhitevvizard
Posts: 454
Joined: 25 Nov 2018, 10:15
Location: Russia

Re: Preview of changes: scope, function and variable references

15 Mar 2021, 04:08

lexikos wrote:
14 Mar 2021, 23:30
The assignment would need to be dynamic to avoid a load-time error; i.e. %'logger'% :=.
Thank u! That helped to work around "error: this Class cannot be assigned a value" in case the class is declared already!
I think proper module/namespace support might supersede any solutions you come up with.
Yup. Ideally, it could be something similar to Python realization.
more into conditional pre-processing...
(...) Clarity is important with DllCall
I agree. And thank u for additional explanations.

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 66 guests