본문 바로가기
IT 공부/Visual Studio 툴 사용하기

[Visual Studio] - 문자 집합 옵션과 UNICODE 매크로

by exdus3156 2024. 12. 26.

 

 

#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 자료형은 피하는 것이 좋기 때문이다.