Script Compatibility

Due to some fundamental differences between AutoHotkey Basic and AutoHotkey_L (particularly Unicode and x64 builds), scripts written for AutoHotkey Basic may not work as expected on AutoHotkey_L. This document outlines known compatibility problems and solutions.

Unicode vs ANSI

All in-memory strings expected or returned by the built-in commands, functions and operators have a particular binary format, referred to as the native format. What this format is depends on which version of AutoHotkey you are using: Unicode or ANSI. Scripts designed with one particular format in mind will often encounter problems when run on the wrong version of AutoHotkey. If you aren't sure which version you're using, run the following script:

MsgBox % A_IsUnicode ? "Unicode" : "ANSI"

ANSI: Strings are stored in the system default ANSI code page, as in AutoHotkey Basic. Each character is one byte (8 bits).

Unicode: Strings are stored in UTF-16 format, where each character is two bytes (16 bits).

Technically, some Unicode characters are represented by two 16-bit code units, collectively known as a "surrogate pair." Similarly, some ANSI code pages (commonly known as Double Byte Character Sets) contain some double-byte characters. However, these are typically treated as two individual "characters" for convenience and practicality.

Script Files

Note that although each version of AutoHotkey only supports one specific format for strings in memory, a number of different encodings may be used for script source files. If a script contains non-ASCII characters in the wrong encoding, they will be loaded incorrectly. Ahk2Exe and Unicode versions of AutoHotkey_L expect UTF-8 by default, whereas scripts running on an ANSI version expect ANSI. For more information, see Script File Codepage.

VarSetCapacity

VarSetCapacity sets the capacity of a var in bytes. Since the size of a character varies depending on the string format, some calculation may be necessary:

VarSetCapacity(ansi_var,    size_in_chars)
VarSetCapacity(unicode_var, size_in_chars * 2)
VarSetCapacity(native_var,  size_in_chars * (A_IsUnicode ? 2 : 1))
VarSetCapacity(native_var, t_size(size_in_chars))  ; see below
VarSetCapacity will internally add one character to the capacity to ensure the var is null-terminated. However, size_in_chars should generally include the null-terminator for consistency if a string in a non-native format will be stored in the var.

DllCall

When the "Str" type is used, it means a string in the native format of the current build. Since some functions may require or return strings in a particular format, the following string types are available:

Char SizeC / Win32 TypesEncoding
WStr16-bitwchar_t*, WCHAR*, LPWSTR, LPCWSTRUTF-16
AStr8-bitchar*, CHAR*, LPSTR, LPCSTRANSI (the system default ANSI code page)
Str--TCHAR*, LPTSTR, LPCTSTREquivalent to WStr in Unicode builds and AStr in ANSI builds.

If "Str" or the equivalent type for the current build is used as a parameter, the address of the string or var is passed to the function, otherwise a temporary copy of the string is created in the desired format and passed instead. Functions may modify a temporary copy, but must not write to any position to the right of the string's null-terminator. As a general rule, "AStr" and "WStr" should not be used for output parameters.

Note: "AStr" and "WStr" are equally valid for parameters and the function's return value.

In general, if a script calls a function via DllCall which accepts a string as a parameter, one of the following approaches must be taken:

  1. If both Unicode (W) and ANSI (A) versions of the function are available, call the appropriate one for the current build. In the following example, "DeleteFile" is internally known as "DeleteFileA" or "DeleteFileW". Since "DeleteFile" itself doesn't really exist, DllCall automatically tries "A" or "W" as appropriate for the current build:
    DllCall("DeleteFile", "Ptr", &filename)
    DllCall("DeleteFile", "Str", filename)

    In this example, &filename passes the address of the string exactly as-is, so the function must expect a string in the same format as the "Str" type. Note that "UInt" must be used in place of "Ptr" in AutoHotkey Basic, but the resulting code may not be 64-bit compatible.

    Note: If the function cannot be found exactly as specified, AutoHotkey_L appends the "A" or "W" suffix regardless of which DLL is specified. However, AutoHotkey Basic appends the "A" suffix only for functions in User32.dll, Kernel32.dll, ComCtl32.dll, or Gdi32.dll.

  2. If the function only accepts a specific type of string as input, the script may use the appropriate string type:
  3. DllCall("DeleteFileA", "AStr", filename)
    DllCall("DeleteFileW", "WStr", filename)
  4. If the function must modify a string (in a non-native format), the script must allocate a buffer as described above and pass its address to the function. If the parameter accepts input, the script must also convert the input string to the appropriate format; this can be done using StrPut.

NumPut / NumGet

When NumPut or NumGet are used with strings, the offset and type must be correct for the given type of string. The following may be used as a guide:

;  8-bit/ANSI   strings:  size_of_char=1  type_of_char="Char"
; 16-bit/UTF-16 strings:  size_of_char=2  type_of_char="UShort"
nth_char := NumGet(var, (n-1)*size_of_char, type_of_char)
NumPut(nth_char, var, (n-1)*size_of_char, type_of_char)

If var contains a string in the native format, the appropriate values may be determined based on the value of A_IsUnicode:

nth_char := NumGet(var, t_size(n-1), t_char())
NumPut(nth_char, var, t_size(n-1), t_char())

; Define functions for convenience and clarity:
t_char() {
    return A_IsUnicode ? "UShort" : "Char"
}
t_size(char_count=1) {
    return A_IsUnicode ? char_count : char_count*2
}

Pointer Size

Pointers are 4 bytes in 32-bit builds (including AutoHotkey Basic) and 8 bytes in 64-bit builds. Scripts using structures or DllCalls may need to account for this to run correctly on both platforms. Specific areas which are affected include:

For size and offset calculations, use A_PtrSize. For DllCall, NumPut and NumGet, use the Ptr type where appropriate.

Remember that the offset of a field is usually the total size of all fields preceding it. Also note that handles (including types like HWND and HBITMAP) are essentially pointer-types.

/*
  typedef struct _PROCESS_INFORMATION {
    HANDLE hProcess;    // Ptr
    HANDLE hThread;
    DWORD  dwProcessId; // UInt (4 bytes)
    DWORD  dwThreadId;
  } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
*/
VarSetCapacity(pi, A_PtrSize*2 + 8) ; Ptr + Ptr + UInt + UInt
DllCall("CreateProcess", <omitted for brevity>, "Ptr", &pi, <omitted>)
hProcess    := NumGet(pi, 0)         ; Defaults to "Ptr".
hThread     := NumGet(pi, A_PtrSize) ; 
dwProcessId := NumGet(pi, A_PtrSize*2,     "UInt")
dwProcessId := NumGet(pi, A_PtrSize*2 + 4, "UInt")

Other Changes

Default Script

When AutoHotkey_L is launched without specifying a script, the default script filename is the same as the filename of the current executable but always has the "ahk" extension. For more details, see Passing Command Line Parameters to a Script.

Variable and Function Names

Characters [ ] and ? are no longer valid in variable names. Consequently, ? (used in ternary operations) no longer requires a space on either side. See also object syntax.

Syntax Validation

Command names must be terminated with a space, tab or comma. Unlike in AutoHotkey Basic, none of the following characters bypass this requirement: <>:+-*/!~&|^[]. As a consequence, syntax errors such as MsgBox< foo and If!foo are caught at load-time, not interpreted as MsgBox,< foo or If !foo.

Transform

Some Transform sub-commands have changed in the Unicode build:

If var is

This command ignores the system locale unless StringCaseSense, Locale has been used.

FileRead

FileRead translates text between code pages in certain common cases and therefore might output corrupt binary data.

Control-Z

Loop Read and FileReadLine do not interpret the character Control-Z (0x1A) as an end-of-file marker. Thus any Control-Z, even one appearing at the very end of the file, is loaded as-is. FileRead already behaved this way.

SetFormat, Integer[Fast], H

When an uppercase H is used, hexadecimal digits A-F will also be in uppercase. This contrasts with AutoHotkey Basic, which always uses lowercase digits. See SetFormat.

Compatibility Mode

If Compatibility mode is set to Windows 95, 98/ME or NT4 in the properties of the EXE file used to run the script, the script may not behave correctly. This is because compatibility mode causes a specific version of Windows to be reported to the application, but the pre-built binaries exclude support for these versions. For example, setting compatibility mode to Windows 95 or 98/ME will cause MsgBox %A_OSVersion% to report WIN_NT4.

Run / RunWait [AHK_L 57+]

In addition to custom verb support (Run *verb file), revision 57 made some changes to the way the action and its parameters are extracted from the Target parameter. Specifically: