[Visual Studio] - 문자 집합 옵션과 UNICODE 매크로
#1. [문자 집합] 옵션은 문자열의 인코딩과 별개다.
▷ 프로젝트 속성 페이지에는 위와 같이 [문자 집합] 설정 옵션이 있다. 여기서 대부분의 Visual Studio 프로젝트는 기본값으로 "유니코드 문자 집합 사용"이 선택된다.
▷ 예전에 나는 이것을 오해하여, 유니코드 문자 집합을 사용하니까 소스코드의 문자열이 UTF-16으로 인코딩되는 건가? 라고 생각했다. 그러나 문자열이 어떻게 인코딩되어 있는지 빌드된 실행 파일의 바이너리를 살펴보면 유니코드와 전혀 상관 없이 인코딩된 모습을 볼 수 있다.
▷ "hello한글"이라는 문자열이 위와 같이 인코딩되었다. 이것은 유니코드가 아니라 CP949다. 이와 관련한 내용을 자세히 포스팅한 링크를 보자 → (링크).. 요약하자면 문자열("")은 MSVC가 로컬에 설정된 MBCS 기반의 문자열로 인코딩한다는 것이며, [문자 집합] 옵션과는 별개라는 것이다.
#2. UNICODE 매크로 정의
▷ 그렇다면 Visual Studio의 문자 집합 옵션은 어떤 의미인가? 그것은 바로 UNICODE 매크로 정의 여부를 말한다.
▷ 위와 같은 간단한 예제 코드를 빌드하고 실행하면 "UNICODE defined" 문자열이 출력된다. 그러면 [문자 집합] 옵션을 "유니코드 문자 집합 사용" 말고 다른 것을 선택하고 빌드하면 어떨까?
▷ 위와 같이 설정하고 빌드하면 놀랍게도 "no UNICODE"가 출력된다!
▷ [문자 집합] 옵션은 UNICODE 매크로 정의와 관련된 옵션이며, 위와 같이 컴파일러의 전처리 옵션으로 "UNICODE"를 정의하라는 명령으로 설정된다.
▷ 만약 옵션을 "멀티바이트 문자 집합 사용"으로 변경하면 위와 같이 "_MBCS" 매크로를 정의하라는 전처리 옵션을 볼 수 있다.
#3. 언제 구분해야 하는가?
▷ 이 UNICODE 매크로의 사용여부는 Windows API를 사용한 개발에서 상당히 중요하게 다뤄지는 부분이다. 따라서 <windows.h>를 포함하고 Windows API를 사용할 때는 ANSI/유니코드 사용여부가 굉장히 중요하다. 아니면 아래와 같은 오류를 만나게 된다. 보통 문자열을 ""으로 사용하므로 처음에 굉장히 자주 실수하는 부분이다.
▷ Windows API 중 하나인 파일 생성 함수 CreateFile()을 사용한 코드다. 그런데 오류를 뱉어내고 있다. 첫 번째 인자 "some.txt"의 타입인 const char*을 LPCWSTR로 암묵적 형변환이 안 된다고 말한다. 왜 변환을 시도하는 것인가. CreateFile()이 첫 번째 인자를 LPCWSTR로 받고 있기 때문이다.
▷ CreateFile() 함수는 위와 같이 UNICODE 매크로가 정의되면 CreateFileW() 함수로 전처리된다. 즉, 유니코드 기반의 API이므로 첫 번째 인자를 유니코드 기반으로 정의된 문자열(궁극적으로 wchar_t 타입)을 인자로 받는 것이다. 이 함수에다 char 타입의 문자열을 제공했으니 타입 변환 오류를 일으키는 것이다.
▷ Windows API는 대부분의 API를 두 가지 버젼, ANSI 문자열 타입과 UNICODE를 처리하는 API를 따로 제공해주며, UNICODE 매크로 정의 여부에 따라 자동으로 하나가 선택될 수 있도록 설계되었다.
▷ 따라서 위 예제를 현명하게 해결하기 위해서는 문자열도 UNICODE 정의 여부에 따라 ANSI 혹은 L 문자열로 정의될 수 있도록 <windows.h>에서 제공하는 텍스트 매크로 함수를 써야 한다. 혹여나 이것을 해결하기 위해 [문자 집합] 옵션을 "멀티바이트 사용"으로 설정하는 경우도 있는데, 이렇게 하면 오류는 일어나지 않지만 논리적으로 소스코드가 일관적이지 않으므로 이렇게 해결해선 안 된다.
#4. 왜 두 버전을 지원해줄까?
▷ 우선 Windows 개발의 표준은 UNICODE다. 그래서 Visual Studio가 기본값으로 [문자 집합]을 "유니코드 사용"으로 설정해주는 것이다. Windows API도 유니코드(UTF-16)으로 문자열을 처리한다. ANSI 기반의 레거시 Windows API도 내부적으로는 유니코드로 번역하는 번거로운 과정을 거친다.
▷ 그렇다면 의문이 든다. 실질적 표준이 UNICODE라면 왜 굳이 MBCS 기반의 Windows API를 지원해주는 것이며, 또 소스코드를 작성할 때 전처리 과정에서 이 두 가지 중 하나를 선택하도록 UNICODE 매크로를 작성하는 스타일로 틀이 잡혔을까? 사실상 wchar_t 타입을 사용할 거라면, 처음부터 이것으로 고정해서 사용하면 안 되는 것일까? 나의 소스코드의 빌드 옵션을 꼭 제공해줘야 하는 것일까?
▷ 만약 처음부터 UNICODE 기반으로 개발하겠다고 작정한다면야 UNICODE 기반의 소스코드를 작성할 수도 있다. 즉 매크로를 사용하지 않고 바로 wchar_t 타입과 L"" 문자열을 코딩하는 것이다. 이렇게 해도 상관은 없으나, 문제는 Windows API 기반 개발 환경 자체가 MBCS 기반도 지원할 수 있는 호환성 높은 소스코드를 작성하도록 설계되었다는 점이다.
▷ 원칙 자체는 동일한 소스코드로 MBCS와 UNICODE 모두를 지원하자! 라는 원칙이고, Windows API도 이 원칙에 맞게끔 다양한 매크로로 래핑되며 개발자에게 제공되고 있다.
▷ 따라서 이러한 스타일로 Windows 개발이 표준화되었기 때문에 이를 피하고 일부러 wchat_t와 L""을 하드코딩하는 것이 오히려 더 번거로울 수 있다. 개인적으로 나는 이 이유가 와닿는다.
▷ 기타 다른 이유로는 구버전의 Windows가 UNICODE를 지원해주지 않을 수 있다는 점, 다른 소스코드나 라이브러리가 내부적으로 레거시 기반으로 동작하는 경우(문자열이 MBCS로 인코딩되어 사용됨) 그 모듈과 함께 사용하려면 MBCS 기반으로 내 소스코드를 다시 빌드해야 하는 등의 이유가 있다. 한 모듈은 MBCS이고 다른 모듈은 UNICODE라면 문자열 데이터를 주고 받는 함수 호출 시 굉장히 신중해져야 할 것이다. 이건 확실히 부담스럽다.
▷ 물론 그렇다고 반드시 TCHAR 처럼 마이크로소프트 windows 자료형을 고집할 이유도 없다. 프로젝트 성격에 따라 자료형을 아예 새롭게 정의할 수도 있을 것이고, 크로스 플랫폼을 고려한다면 오히려 Windows 자료형은 피하는 것이 좋기 때문이다.