이진 호환성

이진 호환성 (Binary Compatibility)

외부 라이브러리를 다루거나 컨트롤·창에 메시지를 보낼 때 때로 중요한 주제들을 설명하는 문서예요.

출처: 문서

본문

목차 (Table of Contents)

  • Buffer
  • DllCall
  • NumPut / NumGet
  • Pointer Size (포인터 크기)

Unicode 대 ANSI (Unicode vs ANSI)

참고: 이 섹션은 문서의 다른 부분에서 다루는 주제에 기반해요: Strings(문자열), String Encoding(문자열 인코딩).

문자열(텍스트 값) 안에서 각 문자의 숫자 문자 코드와 (바이트 단위) 크기는 문자열의 인코딩에 따라 달라져요. 이 세부 사항은 보통 다음 중 하나라도 하는 스크립트에 중요해요:

  • DllCall을 통해 외부 함수에 문자열을 전달.
  • PostMessage 또는 SendMessage로 문자열을 전달.
  • NumPut/NumGet*으로 문자열을 직접 조작.
  • 특정 수의 문자를 담을 Buffer를 할당.

AutoHotkey v2는 기본적으로 Unicode(UTF-16)를 사용하지만, 일부 외부 라이브러리나 창 메시지는 ANSI 문자열을 요구할 수 있어요.

ANSI: 각 문자는 1바이트(8비트)예요. 127보다 큰 문자 코드는 시스템의 언어 설정(또는 파일에 쓸 때처럼 텍스트가 인코딩될 때 선택된 코드 페이지)에 따라 달라져요.

Unicode: 각 문자는 2바이트(16비트)예요. 문자 코드는 UTF-16 형식이 정의한 대로예요.

의미적 참고: 기술적으로 일부 Unicode 문자는 개의 16비트 코드 유닛으로 표현되며, 이를 통틀어 "서러게이트 쌍(surrogate pair)"이라고 불러요. 마찬가지로 일부 ANSI 코드 페이지(흔히 더블바이트 문자 집합(Double Byte Character Sets)이라고 알려진 것)에는 더블바이트 문자가 포함돼요. 하지만 실용적인 이유로 이것들은 거의 항상 두 개의 개별 유닛(간단히 "문자"라고 불림)으로 취급돼요.

Buffer

Buffer를 할당할 때, 필요한 인코딩에 맞게 올바른 바이트 수를 계산하도록 주의해요. 예를 들어:

ansi_buf  := Buffer(capacity_in_chars)
utf16_buf := Buffer(capacity_in_chars * 2)

ANSI 또는 UTF-8 문자열이 StrPut로 버퍼에 쓰여질 것이라면, ANSI/UTF-8 길이가 네이티브(UTF-16) 길이와 다를 수 있으므로 버퍼 크기를 결정하는 데 StrLen을 사용하지 마세요. 대신 StrPut를 사용해 필요한 버퍼 크기를 계산해요. 예를 들어:

required_bytes := StrPut(source_string, "cp0")
ansi_buf := Buffer(required_bytes)
StrPut(source_string, ansi_buf)

DllCall

"Str" 유형이 사용되면, 그것은 현재 빌드의 네이티브 형식 문자열을 의미해요. 일부 함수는 특정 형식의 문자열을 요구하거나 반환할 수 있으므로, 다음 문자열 유형을 사용할 수 있어요:

유형 문자 크기 C / Win32 유형 인코딩
WStr 16비트 wchar_t*, WCHAR*, LPWSTR, LPCWSTR UTF-16
AStr 8비트 char*, CHAR*, LPSTR, LPCSTR ANSI (시스템 기본 ANSI 코드 페이지)
Str -- TCHAR*, LPTSTR, LPCTSTR AutoHotkey v2에서 WStr와 동일

매개변수에 "Str" 또는 "WStr"이 사용되면 문자열의 주소가 함수에 전달돼요. "AStr"의 경우 문자열의 임시 ANSI 복사본이 만들어지고 그 주소가 대신 전달돼요. 일반적인 규칙으로 "AStr"은 버퍼가 입력 문자열을 담을 만큼만 커서 출력 매개변수에는 사용하면 안 돼요.

참고: "AStr"과 "WStr"은 매개변수와 함수의 반환 값 모두에 똑같이 유효해요.

일반적으로 스크립트가 문자열을 매개변수로 받아들이는 함수를 DllCall로 호출한다면, 다음 접근 방식 중 하나 이상을 취해야 해요:

DllCall("DeleteFile", "Ptr", StrPtr(filename))
DllCall("DeleteFile", "Str", filename)
  1. 함수의 Unicode(W) 및 ANSI(A) 버전이 모두 제공된다면, W 또는 A 접미사를 빼고 입력 매개변수나 반환 값에 "Str" 유형을 사용해요. 예를 들어, DeleteFile 함수는 kernel32.dll에서 DeleteFileA와 DeleteFileW로 내보내져요. DeleteFile 자체는 실제로 존재하지 않으므로 DllCall이 자동으로 DeleteFileW를 시도해요. 두 경우 모두 원래 수정되지 않은 문자열의 주소가 함수에 전달돼요. 어떤 경우에는 이 접근 방식이 역효과를 낼 수 있는데, DllCall은 원래 이름의 함수를 찾을 수 없을 때만 W 접미사를 추가하기 때문이에요. 예를 들어, shell32.dll은 접미사가 없는 ExtractIconExW, ExtractIconExA 및 ExtractIconEx를 내보내며, 마지막 둘은 동일해요. 그 경우 W 접미사를 빼면 ANSI 버전이 호출돼요.
DllCall("DeleteFileA", "AStr", filename)
DllCall("DeleteFileW", "WStr", filename)
  1. 함수가 특정 유형의 문자열을 입력으로 받아들인다면, 스크립트는 적절한 문자열 유형을 사용할 수 있어요.

  2. 함수에 출력으로 사용되는 문자열 매개변수가 있다면, 스크립트는 위에 설명한 대로 버퍼를 할당해서 그 함수에 전달해야 해요. 매개변수가 입력을 받아들인다면, 스크립트는 입력 문자열을 적절한 형식으로 변환해야 하며, 여기에 StrPut을 사용할 수 있어요.

NumPut / NumGet

NumPut 또는 NumGet을 문자열과 함께 사용할 때, 오프셋과 유형은 주어진 문자열 유형에 맞아야 해요. 다음을 안내로 사용할 수 있어요:

*;  8비트/ANSI   문자열:  size_of_char=1  type_of_char="UChar"
; 16비트/UTF-16 문자열:  size_of_char=2  type_of_char="UShort"*
*n*th_char := NumGet(buffer_or_address, (*n*-1)*size_of_char, type_of_char)
NumPut(type_of_char, *n*th_char, buffer_or_address, (*n*-1)*size_of_char)

첫 번째 문자의 경우 n은 값 1을 가져야 해요.

포인터 크기 (Pointer Size)

포인터는 32비트 빌드에서 4바이트이고 64비트 빌드에서 8바이트예요. 구조체나 DllCall을 사용하는 스크립트는 두 플랫폼에서 올바르게 실행되려면 이것을 고려해야 할 수 있어요. 영향을 받는 특정 영역은 다음과 같아요:

  • 하나 이상의 포인터를 포함하는 구조체의 필드에 대한 오프셋 계산.
  • 하나 이상의 포인터를 포함하는 구조체의 크기 계산.
  • DllCall, NumPut 또는 NumGet과 함께 사용되는 유형 이름.

크기와 오프셋 계산에는 A_PtrSize를 사용해요. DllCall, NumPut, NumGet에는 적절한 곳에 Ptr 유형을 사용해요.

필드의 오프셋은 보통 그 앞에 오는 모든 필드의 총 크기라는 점을 기억하세요. 또한 핸들(HWND, HBITMAP 같은 유형 포함)은 본질적으로 포인터 유형이라는 점을 주목해요.

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

더 알아보기 (Learn more)