Understanding the Layout of an AutoHotkey Script Helps When Writing and Debugging Applications
The Problem with AutoHotkey Script Design
If you’re anything like me, then you probably jump straight into writing a script without reading the online documentation. Experienced programmers don’t have much problem with this, although even they occasionally find themselves scratching their heads about a particular piece of code which seemingly gets ignored by AutoHotkey.
When I first started working with AutoHotkey, I ran into coding problems because I didn’t properly understand how AutoHotkey reads a file or how to structure an AutoHotkey script. I found myself wondering why AutoHotkey ignored some of my code while other snippets broke the script. In the beginning, I had trouble resolving the issues involving IF conditionals and #IF directives because I didn’t truly understand the difference between the two. I learned to script through trial and error eventually comprehending the nuances of AutoHotkey script structure. I sympathize with new AutoHotkey users—especially if they have never written any programs before.
Writing an AutoHotkey Script
Part of the beauty of writing AutoHotkey apps lies in the fact that just one line of code in a script can offer immediate benefits. Scripts may consist of numerous Hotstrings replacements and Hotkey subroutines. The simplicity of coding Hotstrings and Hotkeys leads us to believe that the order of the code in an AutoHotkey script doesn’t matter—and in many cases, it doesn’t. However, as we design more powerful apps, the order of the AutoHotkey elements takes on more importance. What begins as deceptively simple tasks evolves into more complex routines making errors and conflicts more likely—especially if we don’t understand how AutoHotkey processes a script.
In addition, when you combine more that one AutoHotkey script with multiple Hotstrings/Hotkeys—and include other handy apps—in the same omnibus file, you run the risk of breaking things. It’s perfectly possible to write intertwining scripts which operate on various levels without interference, but you need to know which parts of an AutoHotkey script display sensitivity and which features don’t care how they get treated.
Although not always practical, “Best Practice” dictates keeping similar script components together while separating the unrelated pieces. However, while it’s easy to get started with AutoHotkey scripts, the optimum script layout is not always immediately apparent. Therefore, I offer this explanation of the various pieces of an AutoHotkey script and the consequences of their interaction. When just starting out, learning the lay of the land and how various pieces and features of AutoHotkey interact (or not) within a script makes life much easier.
If you lack programming experience, you may be a little intimidated by AutoHotkey script writing. Venturing for the first time into the new world of script writing can strain your brain. However, once you start producing results, you’ll find script writing both fun and easier than you thought.
The Order of AutoHotkey Scripts
As might be expected with most programming languages, AutoHotkey starts at the beginning of a script and works it way sequentially to the end. Well…sort of. Over time we come to understand that quite a bit happens before the script actually starts running its first routine. In fact, the existence of Hotkeys and Hotstrings—although simple to implement on their own—add a level of complication to script writing not found in many other programming languages. Plus, many AutoHotkey scripts may include an almost hidden cohabiting sequence of command-like #Directives interlaced with Hotstrings, Hotkeys, subroutines, and functions. This possible interweaving of Hotstrings, Hotkeys, #Directives, Labels (subroutines), and functions adds a level of confusion for the new AutoHotkey script writer. To understand how all theses features work (or don’t work) together, you need a basic understanding of how AutoHotkey processes a script.
Two Phases of AutoHotkey Script Loading
From the AutoHotkey online documentation:
“The program loads the script into memory line by line, and each line may be up to 16,383 characters long. During loading, the script is optimized and validated. Any syntax errors will be displayed, and they must be corrected before the script can run.”
AutoHotkey loads and runs a script in two phases: the optimization phase and the run phase. While AutoHotkey does read a script line-by-line when loading during optimization, it does not execute any action commands until the second run phase. During the optimization phase, “each script is semi-compiled while it is being loaded and syntax-checked.” Only after this processing phase does the auto-execute section (first subroutine) at the top of the script begin running commands.
A script containing only Hotkeys and Hotstrings may not offer any complications, but eventually, as our scripts grow, we need to understand how everything interacts.
The Auto-Execute Section of an AutoHotkey Script
The auto-execute section of an AutoHotkey script always appears at the top of the script and runs before any other subroutines in the script. However, this routine does not start running until AutoHotkey completes the optimization phase—reading the entire file and compiling the code elements into memory.
Once it starts, AutoHotkey continues running the commands in the auto-execute section of the script until it encounters either a Return command (end of subroutine), Hotstring definition, or a Hotkey definition. Any one of the three automatically terminates this opening routine and, after that point, no code in the script executes unless directly called inside the auto-execute section or initiate by another compiled script element (e.g. Hotkey, Menu, GUI control). This means that functions and other Label subroutines run as long as this first routine is not terminated. It also means that Hotstrings and Hotkeys cannot be placed inside this section without interfering with the processing of the initial code.
Each Hotstring (text expansion and replacement) acts as a self-contained piece of code. AutoHotkey reads and compiles Hotstrings during the optimization phase before running any routines. Since any Hotstring blocks further processing, they must appear outside all subroutines or functions—and after the auto-execute section of the script.
Best practice dictates placing Hotstrings toward the end of the file or within a separate file adding to the script with the #Include directive—also placed toward the end of the file. (The #Include directive adds the code from the included file to the script at the position of the directive as if part of the original file.) For more information, see “Beginning Tips for Writing AutoHotkey Scripts (Modular Independence of HotStrings)”
Similar to Hotstrings in their script behavior, Hotkeys (action initiating keyboard and mouse combinations) replicate the same script blocking action. While you may place the self-contained Hotkey definition (terminated with the Return command) virtually anywhere within a script, it cannot appear within any subroutine or function. Again, best practice may dictate placing Hotkeys toward the end of a file, but the special use of the #IF directives (which only affect Hotstrings and Hotkeys) may require paying extra attention to both Hotstring and/or Hotkey position within the script. Examples of these #IF techniques are discussed in Chapters Three and Six of my book AutoHotkey Hotkey Techniques. For more information, see Beginning Tips for Writing AutoHotkey Scripts (Modular Independence of HotKeys).
Understanding that the Label name (MyLabel:) appearing within a script merely marks for future reference that program location or address during the optimization phase. This allows AutoHotkey commands (e.g. GoTo, GoSub, Menu) to later jump directly to that spot and run the code. Since once loaded AutoHotkey ignores their existence (unless specifically called to that location by another command), you can add as many Label names as you like almost anywhere in a script. Even then, if no subroutine follows the Label name, nothing happens—although, just as with the auto-execute section, AutoHotkey continues running commands until it encounters the Return command, a Hotstring, or a Hotkey.
Label subroutines ignore #Directives and function definitions since after the optimization phase those elements become invisible to running subroutines. Called functions (the name of the function with parentheses and any parameters, i.e. MyFunction(para1,para2), on a single line) do run within subroutines—just as any other AutoHotkey command.
For convenience, debugging, and easy modification, you should group your Label subroutines together in the script according to their purpose. You can also group Label subroutines for a particular app in a separate file for inserting into the script with the #Include directive.
Tip: Be sure to terminate your Label subroutines with the Return command. Otherwise, once called, AutoHotkey continues running any additional commands and functions—including those in any subsequent Label subroutines until it encounters either another Return (or another terminating command) or a Hotstring/Hotkey definition.
For more information using Label subroutines in scripts, see “Beginning Tips for Writing AutoHotkey Scripts (Building Blocks Using Labels Containing Subroutines)” and “Beginning Tips for Writing AutoHotkey Scripts (Methods for Running Label Subroutines).”
Similar to Label subroutines (with some important differences), functions definitions are an odd animal. They can appear virtually anywhere in a script except within another function definition. In fact, some programmers prefer to place their function definitions within the auto-execute section where they can include outside variable parameters for immediate setup. Most importantly, function definitions do not interfere with the processing of AutoHotkey code. After the optimization phase where all functions get pre-compiled, AutoHotkey no longer sees the functions unless called by the code.
I recommend placing functions within the script according to their application or using the #Include directive to add a separate file of functions-only to the script. For more information, see “Beginning Tips for Writing AutoHotkey Scripts (Functions as Building Blocks).”
Script #Directives behave in a manner similar to commands except that AutoHotkey only accesses and compiles them (settings or actions) during the optimization phase of script loading. (You can find a list of AutoHotkey #Directives at the bottom of the right-hand column on the AutoHotkey Commands page under the Content tab.) You can distinguish directives from commands by the hash mark (#) in front of each. Only directives use the leading # symbol—not to be confused with the # key acting as a modifier (Windows key ) for Hotkey combinations.
The loading script only processes #Directives during the initial optimization phase when AutoHotkey compiles the directive settings or actions into memory. After that point, AutoHotkey no longer sees #Directives—although any embedded conditions remain in effect.
Some #Directives are positional (location matters) while others can appear anywhere in the script. The last occurrence of a #Directive sets the final value for that particular setting or action. Once loaded the #Directive settings cannot be changed, although some associated AutoHotkey commands exist which can alter the initial settings of directives. All directives become invisible to AutoHotkey in the run phase of the script and have no effect on the flow of subroutines.
However, the when using the #IF directives to restrict or modify Hotstrings or Hotkey, AutoHotkey must check for those pre-compiled conditions on each Hotstring/Hotkey activation. This may slow the responsiveness of some Hotstring/Hotkey subroutines.
Note: One common beginning error occurs when new script writers place AutoHotkey commands between various forms of the #IF directives (e.g. #IfWinExist, #IfWinActive). AutoHotkey will ignore those commands since the #IF directives only interact with Hotstring and Hotkey definitions.
Check the individual descriptions for #Directives in the online documentation to determine if they have any positional properties. For more information, see my book AutoHotkey Hotkey Techniques. This book contains some excellent examples of how to use the #IF directives with Hotkeys.
The bulk of the AutoHotkey scripting language consists of commands which do the hard work in functions and subroutines. AutoHotkey commands (as opposed to #Directives) only run after the initial optimization phase completes and the run phase begin—starting with the auto-execute section. Commands make up the action routines whether running in the auto-execute section, within Label subroutines and functions or as the active components of Hotkeys and, possibly, Hotstrings. Commands only launch when contained in or called by one of these elements or another AutoHotkey command. Forget about placing them anywhere else in a script.
The Problem with Combining Multiple AutoHotkey Apps in One Script
The design of AutoHotkey script structure suits writing individual apps better than omnibus files which attempt to combine everything you want to do into one. The single auto-execute section makes AutoHotkey prone to conflicts when commingling multiple scripts. Since each app often uses its own auto-execute section for setup, when integrating scripts, you may find it necessary to parse and meld the various autorun sections—thus creating a multi-purpose auto-execute section at the top of the new file.
You may also need to pay particular attention to variable names when combining apps. If you later integrate diverse scripts using the same variable names, the interaction may cause conflicts.
The same problem occurs when using the AutoHotkey Gui command. If you don’t make a point of designating Gui names when you add them to the same script:
Gui, MyGui:Add, Text,, Text for about-box. Gui, MyGui:Show
you run the risk of GUI’s inadvertently attempting to combine two separate pop-ups into one GUI. Most of the AutoHotkey examples I’ve offered over the years only use only one GUI, therefore I rarely ever used the naming syntax. As a best practice, you should probably make the use of the GUI naming convention second nature when writing a script.
Putting It All Together
Ultimately, (and with the exceptions mentioned above) you can put together an AutoHotkey script almost any way you like. However, for you own convenience and debugging efforts, you should organize your script for easy access to the code. Plus, treat the various elements of your scripts as independent modules which you can place almost anywhere in your scripts. See these tips for new script writers.