jeeswg's functions tutorial

Helpful script writing tricks and HowTo's
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

jeeswg's functions tutorial

24 Dec 2017, 11:58

==================================================

JEESWG'S FUNCTIONS TUTORIAL
[updated: 2019-12-05]

==================================================

CONTENTS

> BUILT-IN / CUSTOM FUNCTIONS
> OUTLINE
> LOCAL / GLOBAL / SUPER-GLOBAL VARIABLES
> STATIC VARIABLES
> BYREF PARAMETERS
> VARIADIC FUNCTIONS
> OMIT PARAMETERS: CUSTOM FUNCTIONS / COM OBJECTS

> MULTIPLE INPUT PARAMETERS
> MULTIPLE OUTPUT PARAMETERS
> INPUT PARAMETERS: COUNT
> INPUT PARAMETERS: CHECK IF PARAMETER OMITTED
> DUAL ROLE INPUT/OUTPUT PARAMETER (BYREF AND OBJECTS)
> PARAMETERS ARE CLEARED AFTER USE
> HANDLE BINARY DATA
> PARAMETER VARIABLES VERSUS OTHER VARIABLES
> CREATING VARIABLES DYNAMICALLY WITHIN FUNCTIONS
> REFERRING TO FUNCTIONS DYNAMICALLY
> OVERRIDE BUILT-IN FUNCTIONS
> #INCLUDE PATH, #INCLUDE <LIBNAME> AND AUTO-INCLUDE
> PERFORM ACTIONS AT SCRIPT STARTUP
> COMBINE SCRIPTS: A SCRIPT WITH MULTIPLE AUTO-EXECUTE SECTIONS
> RECURSIVE FUNCTIONS

> FAT ARROW FUNCTIONS (AHK V2)
> NESTED FUNCTIONS (AHK V2)
> CLOSURES (AHK V2)

> LINKS

==================================================

> BUILT-IN / CUSTOM FUNCTIONS

In AutoHotkey you can use built-in functions.

Code: Select all

vPos := InStr("abcde", "c")
MsgBox, % vPos ;3

vText := StrReplace("abcde", "c", "_")
MsgBox, % vText ;ab_de

vText := RegExReplace("abcde", "[ae]", "_")
MsgBox, % vText ;_bcd_

oArray := StrSplit("a,b,c,d,e", ",")
MsgBox, % oArray[3] ;c
MsgBox, % oArray.3 ;c
In AutoHotkey, you can also create custom functions.

Code: Select all

;E.g. here we create a custom 'Add' function.
vNum1 := 11
vNum2 := 22
vOutput1 := 11 + 22
vOutput2 := vNum1 + vNum2
vOutput3 := 11 + vNum2
vOutput4 := Add(11, 22)
vOutput5 := Add(vNum1, vNum2)
vOutput6 := Add(11, vNum2)
MsgBox, % vOutput1 "`r`n" vOutput2 "`r`n" vOutput3 "`r`n" vOutput4 "`r`n" vOutput5 "`r`n" vOutput6 "`r`n"

Add(var1, var2)
{
	return var1 + var2
}

;E.g. here we create a custom 'Concatenate' function.
vText1 := "abc"
vText2 := "def"
vOutput1 := "abc" "def"
vOutput2 := vText1 vText2
vOutput3 := "abc" vText2
vOutput4 := Concatenate("abc", "def")
vOutput5 := Concatenate(vText1, vText2)
vOutput6 := Concatenate("abc", vText2)
MsgBox, % vOutput1 "`r`n" vOutput2 "`r`n" vOutput3 "`r`n" vOutput4 "`r`n" vOutput5 "`r`n" vOutput6 "`r`n"

Concatenate(var1, var2)
{
	return var1 var2
}
Ultimately you can create functions to do virtually anything. And the vast majority of AutoHotkey's built-in functions can be recreated as custom functions.

Examples of functions:
Explorer window interaction (folder windows/Desktop, file/folder enumeration/selection/navigation/creation) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=35041
GUIs via DllCall: get/set internal/external control text - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=40514
commands as functions (AHK v2 functions for AHK v1) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=29689
AutoHotkey via DllCall: AutoHotkey functions as custom functions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=37871

==================================================

> OUTLINE

Here is a guide to the sections that will follow:

Key terms (which will be explained further):
- scope: local/global/super-global variables - a variable has a local version (inside a function), and a global version (outside a function), sometimes these are the same
- static variables - the content of static variables is remembered after a function is executed, normally variables are cleared
- ByRef parameters - an input parameter that can also be used as an output parameter
- variadic functions - functions with a variable number of parameters, normally functions have a fixed number of parameters

Ways to increase the number of input variables:
- lots of variables as input parameters
- variables containing comma(/delimiter)-separated lists
- use global variables
- use arrays as input parameters
- variadic functions

Ways to increase the number of output variables:
- note: return is limited to one output parameter
- variables containing comma(/delimiter)-separated lists
- use global variables
- return an array
- use multiple ByRef parameters as input/output parameters

Also:
- how many parameters were passed to the function
- omitting parameters / default values
- can an input variable also be an output variable (ByRef, objects)
- are variables inside functions cleared when the function ends
- handling binary data
- are variables inside functions local or global (and: parameter variables versus variables inside the function)
- creating variables dynamically within functions
- referring to functions dynamically
- built-in/custom functions with the same name
- including scripts (and functions) using #Include (and what happens when a function isn't found)
- what happens when a function isn't found
- fat arrow functions (AHK v2)
- nested functions (AHK v2)
- closures (AHK v2)

==================================================

> LOCAL / GLOBAL / SUPER-GLOBAL VARIABLES

- Variables within the body of a function or class definition are either super-global, global or local.
- Variables in the main body of a script are either super-global or global.
- Parameter variables are neither super-global, global nor local, they are special cases.
- It is possible for a global variable, and a variable local to a function, to share the same name, however, they are completely separate variables.

Code: Select all

varG := "G" ;global
global varSG := "SG" ;super-global

Func1(varP1, varP2, varP3) ;parameter variables
{
	global varG2 := "G2" ;global
	static varS := "S" ;static (+ local)
	varX := "X" ;local (it is local as that is the default)
}

Func2(varP1, varP2, varP3) ;parameter variables
{
	local varL := "L" ;local
	static varS := "S" ;static (+ local)
	varX := "X" ;global (it is global because a variable above was defined as local)
}
- Defining a variable as global within the main body of a script (outside a function/class definition) e.g. global var, makes it super-global. All functions will use the global version of that variable, apart from any functions that specify to use their own local(/static) version of that variable e.g. local var or static var.
- Defining a variable as global within a function, means that the global version of the variable is used.
- Parameter variables, those used as parameters when calling the function, e.g. Func(var1, var2, var3) are special cases. They are not global/local/static like the other variables that appear within the body of a function.

- AHK v1: If one or more variables are defined as local, all other (non-parameter) variables in the function are defined as global. This is assume-global mode.
- AHK v2: If one or more variables are defined as local, this does not affect any other variables in the function.

- If one or more variables are defined as global, this does not affect any other variables in the function.
- If one or more variables are defined as static (static variables are local) this does not affect any other variables in the function.

- The word 'global' on its own, can be used to make every variable in a function global by default. This is assume-global mode.
- The word 'static' on its own, can be used to make every variable in a function static (and local) by default. This is assume-static mode.
- Prior to AHK v1.1.27. The word 'local' on its own, cannot be used to make every variable in a function local by default, it triggers an error message.
- From AHK v1.1.27 onwards. The word 'local' on its own, can be used to make every variable in a function local by default. This is force-local mode.

Code: Select all

varA := "A"
varB := "B"
global varC := "C"
TestGbl1()
TestGbl2()
TestGbl3()
TestGbl4()
TestGbl5()
TestGbl6()
TestGbl7()
return

TestGbl1() ;local, local, super-global
{
	;all variables are local apart from any super-globals
	MsgBox, % varA "_" varB "_" varC ;__C
}
TestGbl2() ;global, global, super-global
{
	;all variables are global
	global
	MsgBox, % varA "_" varB "_" varC ;A_B_C
}
TestGbl3() ;global, local, super-global
{
	;varA is global
	;all other variables are local apart from any super-globals
	global varA
	MsgBox, % varA "_" varB "_" varC ;A__C
}
;TestGbl4X()
;{
;	;doesn't work (although using 'static varC' instead would work)
;	global varA
;	local varC
;	MsgBox, % varA "_" varB "_" varC
;}
TestGbl4() ;all local
{
	;defining one or more variables as local,
	;makes all other variables global
	local varB, varC
	MsgBox, % varA "_" varB "_" varC ;A__
}
TestGbl5() ;all local
{
	;defining one or more variables as local,
	;makes all other variables global
	local varC
	MsgBox, % varA "_" varB "_" varC ;A_B_
}
TestGbl6() ;global, local, static (+ local)
{
	;this makes varA global and varC static (static variables are local),
	;all other variables are local apart from any super-globals
	global varA
	static varC
	MsgBox, % varA "_" varB "_" varC ;A__
}
TestGbl7() ;global, global, static (+ local)
{
	;all variables are global
	;apart from varC which will be local (static variables are local)
	global
	static varC
	MsgBox, % varA "_" varB "_" varC ;A_B_
}
- Further examples which work on AHK v1.1.27 onwards.

Code: Select all

varA := "A"
varB := "B"
global varC := "C"
TestGbl8()
TestGbl9()
return

TestGbl8() ;all local
{
	;all variables are local
	local
	MsgBox, % varA "_" varB "_" varC ;__
}
TestGbl9() ;global, local, local
{
	;all variables are local
	;apart from varA which is global
	local
	global varA
	MsgBox, % varA "_" varB "_" varC ;__
}
;TestGbl9X()
;{
;	;doesn't work (although it does work if you swap 'global varA' and 'local')
;	global varA
;	local
;	MsgBox, % varA "_" varB "_" varC
;}
- Some examples of defining variables within functions:
- The same syntax works for 'local' and 'static'.
- The first line makes all variables global.
- The other lines demonstrate getting/setting the contents of a global variable.
- There is a concrete example in the codebox after.

Code: Select all

;note: these lines are each individual examples, they are not intended to be used together
global

global var1
global var1 := 1

global var1, var2
global var1 := 1, var2
global var1, var2 := 2
global var1 := 1, var2 := 2

Code: Select all

var1 := "a", var2 := "b"
Func()
MsgBox, % var1 " " var2 ;1 b

Func()
{
	global var1 := 1, var2
	MsgBox, % var1 " " var2 ;1 b
}
- This example demonstrates that position does not matter when making a variable super-global, but that it does matter when setting the variable's contents.
- The first MsgBox shows that 'global var := 1' does not set the contents of var at the start of the script.
- The second MsgBox suggests that 'global var := 1' did make var super-global.

Code: Select all

MsgBox, % var ;(blank)
Func()
MsgBox, % var ;a
global var := 1
MsgBox, % var ;1

Func()
{
	var := "a"
}
==================================================

> STATIC VARIABLES

- Static variables: the contents of a static variable are remembered between function calls. E.g. you could use a static variable to count each time a function is called.
- With a static variable, the only thing that is 'static' ('constant'), is that its contents are remembered between function calls. The variable's address, size, and content can be changed at any time.
- If you use a line such as this: static var := 123, var is only set to that value once (when the script loads), the value is not reset to 123 each time you use the function.
- When the function call is completed, the variable will maintain its value.
- Static lines can be described as 'ghost lines', they are only executed once, after that, they are invisible to the script.

- All static variables are local.
- A local variable is either static or non-static.
- For any (non-parameter) non-static local variables that appear within the body of a function, their content is discarded when the function call has ended.

Code: Select all

Loop, 3
	MsgBox, % TestStc1A() " " TestStc1B()
Loop, 3
	MsgBox, % TestStc2A() " " TestStc2B()
MsgBox, % ColorNameToRGB("red") " " ColorNameToRGB("yellow") ;FF0000 FFFF00
return

TestStc1A()
{
	vCount++
	return vCount
}
TestStc1B()
{
	static vCount
	vCount++
	return vCount
}
TestStc2A()
{
	vText .= "a"
	return vText
}
TestStc2B()
{
	static vText
	vText .= "a"
	return vText
}
ColorNameToRGB(vName)
{
	static oArray := {red:"FF0000",yellow:"FFFF00",green:"00FF00",blue:"0000FF"}
	return oArray[vName]
}
==================================================

> BYREF PARAMETERS

- In AHK, function parameters are either ByVal (by value), the default, or ByRef (by reference), specified by using 'ByRef'.
- A ByRef parameter is an input parameter that can also be used as an output parameter.
- If you pass a variable into a ByRef parameter, the function can modify that variable.

- Note: if you pass something that isn't a variable into a ByRef parameter, e.g. 123 or 1+1, since you didn't pass a variable, there is no variable for the function to modify.
- Note: if you pass obj.key, the function receives the value of obj.key, and has no knowledge of obj.key, it cannot modify obj.key in the same way it would modify a variable.
- Note: if you pass a non-variable to a ByRef parameter, the parameter is passed ByVal (a value is passed), and not ByRef (a variable reference is passed).

- In the example below, ByVal parameters (default), and ByRef parameters, are contrasted.

Code: Select all

var1 := "A", var2 := "B"
TestByRef1A(var1, var2)
MsgBox, % var1 var2 ;AB

var1 := "A", var2 := "B"
TestByRef1B(var1, var2)
MsgBox, % var1 var2 ;__

TestByRef1A(var1, var2)
{
	;this will modify the variables inside the function only
	var1 := "_", var2 := "_"
}
TestByRef1B(ByRef var1, ByRef var2)
{
	;this will modify the variables inside and outside the function
	var1 := "_", var2 := "_"
}

- In the example below, pass a variable, and IsByRef returns 1.
- Pass a value, and IsByRef returns 0.

Code: Select all

vText := "abc"
vNum1 := 123
vNum2 := 123.456
oArray := []

MsgBox, % TestByRef(vText) ;1
MsgBox, % TestByRef(vNum1) ;1
MsgBox, % TestByRef(vNum2) ;1
MsgBox, % TestByRef(oArray) ;1

MsgBox, % TestByRef("abc") ;0
MsgBox, % TestByRef(123) ;0
MsgBox, % TestByRef(123.456) ;0
MsgBox, % TestByRef(1+1) ;0
MsgBox, % TestByRef([]) ;0

TestByRef(ByRef vItem)
{
	return IsByRef(vItem)
}

- ByRef parameters can be used to return multiple values from a function.

Code: Select all

vPath := A_ScriptFullPath
SplitPath(vPath, vName, vDir, vExt, vNameNoExt, vDrive)
MsgBox, % Format("{}`r`n{}`r`n{}`r`n{}`r`n{}`r`n{}", vPath, vName, vDir, vExt, vNameNoExt, vDrive)

SplitPath(vPath, ByRef vName, ByRef vDir, ByRef vExt, ByRef vNameNoExt, ByRef vDrive)
{
	SplitPath, vPath, vName, vDir, vExt, vNameNoExt, vDrive
}

- Specifying parameters as ByRef, allows data to be passed to functions more quickly.
- Here is a rough benchmark test.
- (Note: there are many considerations, not addressed here, when seeking good benchmark test results.)

Code: Select all

#NoEnv
AutoTrim, Off
SetBatchLines, -1
ListLines, Off

;test ByVal v. ByRef
vLen := 1000000
vCount := 1000000

;vLen *= 10, vCount /= 10 ;much faster
vLen /= 10, vCount *= 10 ;much slower (adding iterations slows more than increasing string length)

vText := Format("{:" vNum "}", "") ;multiple zeros
vText := StrReplace(vText, " ", "a")

Clipboard .= "`r`n"

vTickCount := A_TickCount
Loop % vCount
	vText2 := StrUpperByVal(vText)
Clipboard .= "`t" (A_TickCount - vTickCount)

vTickCount := A_TickCount
Loop % vCount
	vText2 := StrUpperByRef(vText)
Clipboard .= "`t" (A_TickCount - vTickCount)

vTickCount := A_TickCount
Loop % vCount
	vText2 := StrUpperByVal(vText)
Clipboard .= "`t" (A_TickCount - vTickCount)

vTickCount := A_TickCount
Loop % vCount
	vText2 := StrUpperByRef(vText)
Clipboard .= "`t" (A_TickCount - vTickCount)

MsgBox, % "done"
return

StrUpperByVal(vText)
{
	StringUpper, vText, vText
	return vText
}

StrUpperByRef(ByRef vText)
{
	StringUpper, vText, vText
	return vText
}
==================================================

> VARIADIC FUNCTIONS

- Variadic functions can have a variable number of parameters.
- They have 0 or more normal parameters, and then a 'variadic parameter'.
- All of the items in the 'variadic parameter' are retrieved as keys in an array by the function. With key names 1, 2, 3 etc.
- At present, ByRef parameters cannot be used with the 'variadic parameter'.

Code: Select all

MsgBox, % Concatenate1("a", "b", "c")
oArray := ["a", "b", "c"]
MsgBox, % Concatenate1(oArray*)
MsgBox, % Concatenate1(["a", "b", "c"]*)

MsgBox, % Concatenate2("a", "b", "c")

Concatenate1(oParams*)
{
	;a variadic function with no leading normal parameters
	Loop, % oParams.Length()
		vOutput .= oParams[A_Index]
	return vOutput
}
Concatenate2(var1, var2, oParams*)
{
	;a variadic function with leading normal parameters
	vOutput := var1 var2
	Loop, % oParams.Length()
		vOutput .= oParams[A_Index]
	return vOutput
}
==================================================

> OMIT PARAMETERS: CUSTOM FUNCTIONS / COM OBJECTS

- From v1.1.12 onwards, optional parameters can be omitted in functions. This also made the ComObjMissing function unnecessary.
- However, if you wanted a parameter, used with a COM object, to sometimes be omitted, and to sometimes be used, you could use the alternative mentioned below:
jeeswg's objects tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=29232

Code: Select all

m := ComObjMissing() ;AHK v1
m := ComObject(0xA, 0x80020004) ;AHK v1/v2
Changes & New Features
https://autohotkey.com/docs/AHKL_ChangeLog.htm#v1.1.12.00
1.1.12.00 - August 14, 2013
Optional parameters can be omitted by writing two consecutive commas, as in InStr(a, b,, 2). Unlike previous versions, this now works for objects (including COM objects) and built-in functions. [a,,b] can be used to create a sparse array.
ComObjActive()
https://autohotkey.com/docs/commands/ComObjActive.htm
Creates an object which may be used in place of an optional parameter's default value when calling a method of a COM object. [v1.1.12+]: This function is obsolete. Instead, simply write two consecutive commas, as in Obj.Method(1,,3)
ParamObj := ComObjMissing()
==================================================

> MULTIPLE INPUT PARAMETERS

Ways to increase the number of input variables.

Code: Select all

;lots of input parameters
InFuncParams(var1, var2, var3)
{
	return var1 " " var2 " " var3
}

;string containing comma(/delimiter)-separated list
InFuncSepList(vList, vDelim:="")
{
	oTemp := StrSplit(vList, vDelim)
	return oTemp.1 " " oTemp.2 " " oTemp.3
}

;global variables
InFuncGlobal()
{
	global var1, var2, var3
	return var1 " " var2 " " var3
}

;input parameter array
InFuncLinearArray(oArray)
{
	return oArray.1 " " oArray.2 " " oArray.3
}

;input parameter array
InFuncAssocArray(oArray)
{
	return oArray.key1 " " oArray.key2 " " oArray.key3
}

;variadic function
InFuncVariadic(oParams*)
{
	return oParams.1 " " oParams.2 " " oParams.3
}

vOutput := ""

vOutput .= InFuncParams("abc", "def", "ghi") "`r`n"

vOutput .= InFuncSepList("abc,def,ghi", ",") "`r`n"

var1 := "abc", var2 := "def", var3 := "ghi"
vOutput .= InFuncGlobal() "`r`n"

vOutput .= InFuncLinearArray(["abc", "def", "ghi"]) "`r`n"

vOutput .= InFuncAssocArray({key1:"abc", key2:"def", key3:"ghi"}) "`r`n"

vOutput .= InFuncVariadic("abc", "def", "ghi") "`r`n"

vOutput .= InFuncVariadic(["abc", "def", "ghi"]*) "`r`n"

MsgBox, % vOutput
==================================================

> MULTIPLE OUTPUT PARAMETERS

Ways to increase the number of output variables.

Code: Select all

;string containing comma(/delimiter)-separated list
OutFuncSepList()
{
	var1 := "abc", var2 := "def", var3 := "ghi"
	return var1 "," var2 "," var3
}

;global variables
OutFuncGlobal()
{
	global var1, var2, var3
	var1 := "abc", var2 := "def", var3 := "ghi"
}

;output array
OutFuncLinearArray()
{
	var1 := "abc", var2 := "def", var3 := "ghi"
	return [var1, var2, var3]
}

;output parameter array
OutFuncAssocArray()
{
	var1 := "abc", var2 := "def", var3 := "ghi"
	return {key1:var1, key2:var2, key3:var3}
}

;ByRef parameters
OutFuncByRefParams(ByRef var1, ByRef var2, ByRef var3)
{
	var1 := "abc", var2 := "def", var3 := "ghi"
}

;ByRef parameters
OutFuncByRefArray(ByRef oArray)
{
	var1 := "abc", var2 := "def", var3 := "ghi"
	oArray := [var1, var2, var3]
}

vOutput := ""

vOutput .= OutFuncSepList() "`r`n"

OutFuncGlobal()
vOutput .= var1 "," var2 "," var3 "`r`n"

oArray := OutFuncLinearArray()
vOutput .= oArray.1 "," oArray.2 "," oArray.3 "`r`n"

oArray := OutFuncAssocArray()
vOutput .= oArray.key1 "," oArray.key2 "," oArray.key3 "`r`n"

OutFuncByRefParams(var1, var2, var3)
vOutput .= var1 "," var2 "," var3 "`r`n"

OutFuncByRefArray(oArray)
vOutput .= oArray.1 "," oArray.2 "," oArray.3 "`r`n"

MsgBox, % vOutput
==================================================

> INPUT PARAMETERS: COUNT

How many parameters were passed to the function.

Code: Select all

VariadicFunc(oParams*)
{
	return oParams.Length()
}

MsgBox, % VariadicFunc("a") ;1
MsgBox, % VariadicFunc("a", "b") ;2
MsgBox, % VariadicFunc("a", "b", "c") ;3
==================================================

> INPUT PARAMETERS: CHECK IF PARAMETER OMITTED

Omit parameters / default values.

Code: Select all

OmitFunc1(var1:="default", var2:="default", var3:="default")
{
	return var1 " " var2 " " var3
}

OmitFunc2(oParams*)
{
	var1 := oParams.HasKey(1) ? oParams.1 : "omitted"
	var2 := oParams.HasKey(2) ? oParams.2 : "omitted"
	var3 := oParams.HasKey(3) ? oParams.3 : "omitted"
	return var1 " " var2 " " var3
}

MsgBox, % OmitFunc1(1,, 3)
MsgBox, % OmitFunc1(1, 2)
MsgBox, % OmitFunc1(,, 3)

MsgBox, % OmitFunc2(1,, 3)
MsgBox, % OmitFunc2(1, 2)
MsgBox, % OmitFunc2(,, 3)
==================================================

> DUAL ROLE INPUT/OUTPUT PARAMETER (BYREF AND OBJECTS)

Can an input variable also be an output variable. Passing variables ByRef, and passing objects ByRef/non-Byref.

Code: Select all

FuncInOutStr(ByRef var)
{
	var := "after"
}

var := "before"
MsgBox, % var ;before
FuncInOutStr(var)
MsgBox, % var ;after

oArray.MyKey := "before"
FuncInOutObj(oArray)
{
	oArray.MyKey := "after"
}

obj := {}
obj.MyKey := "before"
MsgBox, % obj.MyKey ;before
FuncInOutObj(obj)
MsgBox, % obj.MyKey ;after
==================================================

> PARAMETERS ARE CLEARED AFTER USE

Are variables inside functions cleared when the function ends.

Code: Select all

;global - not cleared
;ByRef - not cleared

FuncTestClear1()
{
	global addr
	obj := ["a", "b", "c"]
	addr := &obj
	obj2 := Object(addr)
	MsgBox, % obj2.2 ;c
}
FuncTestClear2(ByRef obj2:="")
{
	global addr
	obj := ["a", "b", "c"]
	addr := &obj
	obj2 := Object(addr)
	MsgBox, % obj2.2 ;c
}
FuncTestClear3(obj2:="")
{
	global addr
	obj := ["a", "b", "c"]
	addr := &obj
	obj2 := Object(addr)
	MsgBox, % obj2.2 ;c
}

FuncTestClear1()
;obj2 := Object(addr) ;crashes AHK
MsgBox, % obj2.2

FuncTestClear2()
;MsgBox, % addr
;obj2 := Object(addr) ;crashes AHK
;MsgBox, % obj2.2

FuncTestClear2(obj2)
;MsgBox, % addr
;obj2 := Object(addr) ;crashes AHK
MsgBox, % obj2.2

MsgBox
FuncTestClear3(obj2)
;MsgBox, % addr
;obj2 := Object(addr) ;crashes AHK
MsgBox, % obj2.2
==================================================

> HANDLE BINARY DATA

Using the variable address/ByRef parameters for handling binary data.
Note: in AHK v2, you can output binary data as the return value.

Code: Select all

FuncWriteBinAddr(vAddr)
{
	NumPut(0x11223344, vAddr+0, 0, "UInt")
}
FuncWriteBinVar(ByRef vData)
{
	VarSetCapacity(vData, 4, 0)
	NumPut(0x11223344, &vData, 0, "UInt")
}
FuncWriteBinVarOut() ;AHK v2 only
{
	VarSetCapacity(vData, 4, 0)
	NumPut(0x11223344, &vData, 0, "UInt")
	return vData
}

;address
VarSetCapacity(vData, 4, 0)
FuncWriteBinAddr(&vData)
vNum := NumGet(&vData, 0, "UInt")
MsgBox, % Format("0x{:X}", vNum)

;ByRef
FuncWriteBinVar(vData)
vNum := NumGet(&vData, 0, "UInt")
MsgBox, % Format("0x{:X}", vNum)

;return value
;vData := FuncWriteBinVarOut()
;vNum := NumGet(&vData, 0, "UInt")
;MsgBox, % Format("0x{:X}", vNum)
==================================================

> PARAMETER VARIABLES VERSUS OTHER VARIABLES

Are variables inside functions local or global (and: parameter variables versus variables inside the function).

Code: Select all

;function variables to consider:
;local/global/static
;param (ByRef)/param (non-ByRef)
;(variable used with return)

global vGbl
Func1(vGbl:="")
{
	vGbl := 1 ;does not affect global vGbl because it's a parameter variable
}
Func2()
{
	vGbl := 2
}
vGbl := 0
Func1()
MsgBox, % vGbl
Func2()
MsgBox, % vGbl
==================================================

> CREATING VARIABLES DYNAMICALLY WITHIN FUNCTIONS

Creating variables dynamically within functions.

Code: Select all

;dynamic variables can overwrite global variables
;var1 was already defined within the function body, so the global variables are not overwritten
;var2/var3 were not already defined, so the *global* variables are assigned to
FuncDynamicVars()
{
	var1 := ""
	Loop, 3
		var%A_Index% := A_Index
}
var1 := "a", var2 := "b", var3 := "c"
MsgBox, % var1 " " var2 " " var3 ;a b c
FuncDynamicVars()
MsgBox, % var1 " " var2 " " var3 ;a 2 3

;local used, so no global variables are overwritten
FuncDynamicVarsLocal()
{
	local
	var1 := ""
	Loop, 3
		var%A_Index% := A_Index
}
var1 := "a", var2 := "b", var3 := "c"
MsgBox, % var1 " " var2 " " var3 ;a b c
FuncDynamicVarsLocal()
MsgBox, % var1 " " var2 " " var3 ;a 2 3
==================================================

> REFERRING TO FUNCTIONS DYNAMICALLY

Referring to functions dynamically.
Either by name, or via a Func object created via Func, or via a BoundFunc object created using Bind() which can also specify the contents for the first n parameters.

Code: Select all

FuncConcat(var1, var2, var3)
{
	return var1 var2 var3
}

vFunc := "FuncConcat"
MsgBox, % %vFunc%(1, 2, 3) ;123

oFunc := Func("FuncConcat")
;MsgBox, % IsObject(oFunc) ;1
MsgBox, % %oFunc%(1, 2, 3) ;123

oFunc := Func("FuncConcat")
;MsgBox, % IsObject(oFunc) ;1
MsgBox, % %oFunc%(1, 2, 3) ;123

oFunc := Func("FuncConcat").Bind()
MsgBox, % %oFunc%(1, 2, 3) ;123

oFunc := Func("FuncConcat").Bind(4)
MsgBox, % %oFunc%(2, 3) ;423

oFunc := Func("FuncConcat").Bind(4, 5)
MsgBox, % %oFunc%(3) ;453

oFunc := Func("FuncConcat").Bind(4, 5, 6)
MsgBox, % %oFunc%() ;456
==================================================

> OVERRIDE BUILT-IN FUNCTIONS

Built-in/custom functions with the same name.

Code: Select all

;the built-in function is overridden
Sqrt(vNum)
{
	return "root: " (vNum**0.5)
}
MsgBox, % Sqrt(4)
==================================================

> #INCLUDE PATH, #INCLUDE <LIBNAME> AND AUTO-INCLUDE

Including scripts (and functions) using #Include.
And what happens when a function isn't found (auto-include).

#Include / #IncludeAgain - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/_Include.htm
A script behaves as though the included file's contents are physically present at the exact position of the #Include directive (as though a copy-and-paste were done from the included file).
APPROACH 1: #Include FileFullPath

To include a file explicitly:
#Include C:\Program Files\AutoHotkey\MyFile.ahk

APPROACHES 2/3:

These 2 lines serve a similar purpose:
#Include <MyPrefix_MyFunc>
MyPrefix_MyFunc() ;a call to a function that is not present in the script

#Include <MyPrefix_MyFunc>
This checks 3 'Lib' folders for scripts called 'MyPrefix_MyFunc.ahk', and then checks those 3 'Lib' folders for 'MyPrefix.ahk'. If it finds a file, that file in included as though it were copied and pasted at that location.

MyPrefix_MyFunc()
Assuming that the function 'MyPrefix_MyFunc' isn't already defined. This checks 3 'Lib' folders for scripts called 'MyPrefix_MyFunc.ahk', and then checks those 3 'Lib' folders for 'MyPrefix.ahk'. If it finds a file, that file in included, not as though it were copied and pasted at that location, but in a different way described lower down.

APPROACH 2: #Include <LibName>

To check if 6 possible filenames exist and include the first matching file.
#Include <MyPrefix_MyFunc>

3 library folders are checked: local/user/standard:
%A_ScriptDir%\Lib ;local library
%A_MyDocuments%\Lib ;user library
%A_AhkDir%\Lib ;standard library [there is no such variable in AHK at present][where 'A_AhkDir' would be the dir taken from 'A_AhkPath']

6 files are checked for, listed in the order that they are checked for:
%A_ScriptDir%\Lib\MyPrefix_MyFunc.ahk
%A_MyDocuments%\Lib\MyPrefix_MyFunc.ahk
%A_AhkDir%\Lib\MyPrefix_MyFunc.ahk
%A_ScriptDir%\Lib\MyPrefix.ahk
%A_MyDocuments%\Lib\MyPrefix.ahk
%A_AhkDir%\Lib\MyPrefix.ahk
If no matching file is found, an error is raised:
'Error: Call to nonexistent function.'

APPROACH 3: AUTO-INCLUDE

If a call is done to a function that does not exist:
e.g. 'MyPrefix_MyFunc()'
then that is very similar to doing '#Include <MyPrefix_MyFunc>'
but with one key difference,
again 6 files are checked for a possible match,
however, instead, the file contents are not 'copied and pasted' at the point at which the function was called,
however, the function is executed.
Thus an auto-include 'includes' a script, in the sense that all of its functions are made available for use, however, it does not 'copy and paste' the file contents to a specific point.

Note: If no matching file is found, an error is raised:
'Error: Call to nonexistent function.'

FURTHER NOTE:

- Think of any files that are included as in an abstract place (the error messages even indicate a completely separate line numbering system), away from the main script.
- Both #Include and auto-include cause files to be 'present' in an abstract place. However, any #Include line has an additional effect, any time it is 'executed' in the flow of the script, it effectively executes all of the code in that file (as though it were copied and pasted at that point).

See also:
#Include <LibName> v. auto-include - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=53647

==================================================

> PERFORM ACTIONS AT SCRIPT STARTUP

A function that will run on script startup (due to 'static').

Code: Select all

MyFunc()
{
	static vDummy := MyFunc()
	SoundBeep
	MsgBox, % "hello world"
}
Also mentioned here:
GeekDude's tips and tricks - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=7190
Static init functions (was: Goto Eof) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=13&t=4172&p=23235#p23235
jeeswg's documentation extension tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=33596

==================================================

> COMBINE SCRIPTS: A SCRIPT WITH MULTIPLE AUTO-EXECUTE SECTIONS

- An auto-execute section, is code at the top of a script, executed before the rest of the script.
- If you want to combine multiple scripts by using #Include, only one of those scripts can be at the top of the combined script.
- A workaround, is to use functions that call themselves, when the script starts.
- The principle of a function, that calls itself when the script starts, is outlined above in the section: 'PERFORM ACTIONS AT SCRIPT STARTUP'.

- Here is an example of combining 2 scripts, consisting of a master script, and 2 child scripts:

Code: Select all

;combine 2 scripts - master.ahk

#Include combine 2 scripts - child 1.ahk
#Include combine 2 scripts - child 2.ahk

Code: Select all

;combine 2 scripts - child 1.ahk

AutoExec1()
{
	static vDummy := AutoExec1()
	global vInit1 := 1
}

q::
MsgBox, % vInit1
return

Code: Select all

;combine 2 scripts - child 2.ahk

AutoExec2()
{
	static vDummy := AutoExec2()
	global vInit2 := 2
}

w::
MsgBox, % vInit2
return
For reference, this is what one of the child scripts would look like, with a normal auto-execute section:

Code: Select all

[code]
;combine 2 scripts - child 1 (with normal auto-execute section).ahk

vInit1 := 1

q::
MsgBox, % vInit1
return
==================================================

> FAT ARROW FUNCTIONS (AHK V2)

Code: Select all

;fat arrow functions

;Variables and Expressions - Definition & Usage | AutoHotkey v2
;https://lexikos.github.io/v2/docs/Variables.htm#fat-arrow

sumfn := Sum(a, b) => a + b
MsgBox(%sumfn%(1, 2)) ;3

concatfn := Concat(a, b) => a b
MsgBox(%concatfn%(1, 2)) ;12
==================================================

> NESTED FUNCTIONS (AHK V2)

Code: Select all

;nested functions

;Functions - Definition & Usage | AutoHotkey v2
;https://lexikos.github.io/v2/docs/Functions.htm#nested

outer(x)
{
	inner(y)
	{
		MsgBox(x " " y)
	}
	inner("b")
	inner("c")
}
outer("a")
==================================================

> CLOSURES (AHK V2)

Code: Select all

;closures

;Functions - Definition & Usage | AutoHotkey v2
;https://lexikos.github.io/v2/docs/Functions.htm#closures
;To create a closure, pass the name of a nested function to Func.

make_greeter(format)
{
	greet(subject)
	{
		MsgBox(Format(format, subject))
	}
	return Func("greet")
}

greetfn := make_greeter("hello {}!")
greetfn.call(A_UserName)
greetfn.call("world")
==================================================

> RECURSIVE FUNCTIONS

- A recursive function is a function that calls itself.
- Here, two different functions for calculating factorials are demonstrated, one recursive, one non-recursive.

Code: Select all

q:: ;demonstrating a factorial function
vOutput := ""
Loop, 20
	vOutput .= Fact(A_Index) "`t" FactAlt(A_Index) "`r`n"
MsgBox, % vOutput
return

;non-recursive
Fact(vNum)
{
	local
	if (vNum < 1) || (vNum > 20)
		return
	vOutput := 1
	Loop, % vNum
		vOutput *= A_Index
	return vOutput
}

;a recursive function
FactAlt(vNum)
{
	local
	if (vNum < 1) || (vNum > 20)
		return
	if (vNum = 1)
		return 1
	return vNum * FactAlt(vNum-1)
}
==================================================

> LINKS

[shows an example of a callback function]
GUIs via DllCall: list windows/child windows (controls) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=36405

[Sort supports callback functions]
Sort - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/Sort.htm

[RegExMatch/RegExReplace support callback functions]
Regular Expression Callouts | AutoHotkey
https://autohotkey.com/docs/misc/RegExCallout.htm

[OnClipboardChange/OnError/OnExit/OnMessage support callback functions]
Alphabetical Command and Function Index | AutoHotkey
https://autohotkey.com/docs/commands/

==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 40 guests