본문 바로가기

CS연구소👨‍💻/C++

[C++] WCHAR, TCHAR, cstring 정리

이런 것에 대한 정보를 담고 있다

  1. WCHAR과 TCHAR이 무엇인지
  2. 가변길이 문자열 vs 고정 길이 문자열
  3. cstring string의 차이
  4. WCHAR 문자열 만드는법.

이런 것에 대한 정보는 담겨있지 않다

1. WCHAR과 TCHAR이 무엇인지

우선 WCHAR은 wind-character 이라고 하는 문자이다. 일반적으로 문자는 1바이트라고 배운다. 0부터 127까지의 비트로 문자를 구분해놓은 아스키(ASCII)가 대표적인 1바이트 문자이다. 그럼 당연히 wide string이라는건 2바이트 이상의 문자열을 의미하겠다 라고 생각할 수 있다.

그렇다면 TCHAR은 무엇일까 TCHAR은 1바이트 문자 CHAR과 2바이트 문자 WCHAR을 통일해서 쓰기 위해 만들어졌다. 유니코드를 사용하는 경우 TCHAR은 2바이트 문자를 의미하고, 1바이트 문자열을 사용하는 프로그램의 경우 TCHAR은 1바이트 문자를 의미한다.

LPCTSTR

2. 가변길이 문자열 vs 고정길이 문자열

하지만 막상 C++ 프로그램에서 cout << “안녕, 월드”; 를 치면 그 문자가 그대로 출력이 된다. 이는 사실 문자열이라는 것 중 가변길이 문자열이 있기 때문이다. UTF-8, UTF-16라고 하는 문자열이 가변길이 대표적인 가변길이 문자열이다.

UTF-8과 UTF-16은 Unicode Transformation Format이라는 약자에서처럼 유니코드 형태의 문자인데 둘의 차이는 인코딩되는 문자의 최소 바이트이다.

UTF-8은 8비트, 즉 1바이트 이상의 문자열로 인코딩 될 수 있고, 또 UTF-16 문자열은 2바이트 이상의 문자열로 인코딩 될 수 있다.

만약 우리가 UTF-8 문자 ‘가’를 C++에서 char*의 배열로 바꾸려고한다면.

"가" = 0xEAB080

으로 표현되며 배열로는

char* str[] = { 0xEA, 0xB0, 0x80 } 

으로 바뀌어서 저장이 될 것이다. 그리고 utf-8 방식의 C++ 문자열이 이것을 ‘가’ 라고 읽게된다.

정리하자면, 기본적으로 C++문자열은 UTF-8 형식이기 때문에 char이 1바이트 임에도 한글을 표현할 수 있다. 즉 UTF-8은 1바이트부터 인코딩/디코딩 할 수 있는 시스템/체계이지 1바이트의 문자열 체계가 아니다.

3. cstring, string의 차이

cstring은 C나 C++ 프로젝트들을 찾아보면 반드시 확인할 수 있는 자료구조다 cstring은 c언어에서 문자열을 저장할 때 배열을 쓰던 방식처럼 문자열 배열의 첫번째 요소의 주소값을 담고있는 문자열이다. 그런데 그 배열이 반드시 1바이트 배열일 필요는 없는지 CString은 wChar 문자열도 CString으로 만들 수 있다.

4. WCHAR 문자열 만드는 법

일단 wchar_t 문자를 저장하는 법 부터 알아보면 문자 앞에 ‘L’을 붙인다

wchar_t wchar = L'파';

이러면 같은 문자인 char에비해 2바이트 문자를 저장할 수 있다.

const WCHAR* list = _T("%s", s);
  • [ ] 2바이트 문자 wchar_t에 한글을 할당하게되면 utf-8이 된다

와이드 스트링 대참사라고 오늘 사건을 부르겠다. 일단 저장이 중요하니까

std::wstring_convert<std::codecvt_utf8_utf16<WCHAR>> myconv; std::wstring wstr = myconv.from_bytes(str);

codecvt

CMAKE 그거 → visual studio에서 dll 빌드하는법 보고 빌드해서 언어확인 ㄱㄱ

왜왜왜왜왜왜

일단 입력되는 문자열이 멀티바이트라고 가정 그걸 유니코드로 바꿔버린다.

여기서 잘못되면 그냥 소스에

    // 변환에 필요한 버퍼 크기 계산
    int bufferSize = WideCharToMultiByte(CP_ACP, 0, wideString, -1, NULL, 0, NULL, NULL);
    if (bufferSize == 0) {
        std::cerr << "Error calculating buffer size for conversion." << std::endl;
        return nullptr;
    }

    // 변환된 문자열을 저장할 버퍼 할당
    char* multiByteString = new char[bufferSize];
    
    // 실제 변환 수행
    int result = WideCharToMultiByte(CP_ACP, 0, wideString, -1, multiByteString, bufferSize, NULL, NULL);
    if (result == 0) {
        std::cerr << "Error converting wide char to multi-byte string." << std::endl;
        delete[] multiByteString;
    }

string을 LPCSTR로 바꾸는 법.

왜 안되는지에 대한 감을 아직 못잡았다는거임 문제는

브루트 포스로 해결

  1. 일단 string주소를 그대로 전달 처리하지 않음.
    1. 같은 string이지만 visualstudio에서는 전달된 string을 전혀 읽지 못하는 모습을 보여줌, 그리고
  2. 내가 해당 문자열을 2바이트 문자열로 변경

= 해결

수십만가지처럼 느껴지는 문제를 카테고리화 해보면

  1. 소스 파일의 인코딩 문제 : 잘은 모르겠지만 dll을 만드는데 쓰이는 소스파일이 저장 형식이 ANSI, 즉 utf가 아니면 문제가 생긴다 → locale이 utf였던 gitbash에서 문제없이 소스를 출력했던 것을 생각하면 오류일 확률이 높음.
  2. 빌드상에서 msvc로 빌드를 할때 유니코드를 명시해주지 않았기 때문에 생겼던 오류, 실제로 처음 빌드했을 때 언어가 멀티바이트 형식으로 되어있었기 때문에 가능성 높음 → 그러나 마찬가지로 locale이 utf였던 gitbash가 처음 문제없이 cout, wprintf를 했었기 때문에 완전한 이유인지는 알 수 없음
  3. 소스파일 동기화 문제, 지금 내가 만든 dll은 다른 프로젝트에서 빌드를 하는데. 여기서 사용하던 소스파일 cpp가 dll을 사용하는 프로젝트에서 사용하던 소스파일 cpp와 동기화되지 않았었음. 그래서 문제의 소지가 있었던 코드가 사용되었음. 이건 어떤 프로젝트에서 dll을 로드할때 정의된 소스파일을 먼저 활용하는건지 아니면 dll에 바이너리화 돼있을 다른 소스코드를 사용하는건지가 궁금하고 알아봐야함 *
  4. 문제의 원인은 2번과 3번의 복합이 아닐까 하는 생각이 든다. 예를들어 같은문자를 표현하는데 multibyte의 비트표현식과 유니코드의 비트표현식이 다르면 char* 바이트 배열로 로드한 뒤에 2바이트로 바꾸는 방식이 멀티바이트와 유니코드 사이의 교환에서 적용되지 않을 것 같으니. 그리고 나는 w_char_t* 배열로 첫 문자열을 교체했는데 utf-8형식의 문자를 w_char_t의 utf-16 문자로 변환하려하면 문제가 생기나? 하는 생각이 있음
    1. 하지만 3번은 문제가 되지 않을 것이라는 얘기가 있었음. oldA의 정의보다는 A의 정의를 매핑하는 방식이기 때문에.

그리고 또 문제가 1개

생각해보면 같은 string인데도 2바이트 문자를 전혀 못읽었음, 그리고 원래 string은 utf-8이기 때문에 2바이트 문자열도 읽을 수 있어야함. 그런데 이 특정 프로젝트에서 그것이 실패한다는건 어떤 세팅을 통해서 string이 1바이트 문자열만 읽을 수 있게 조절한것인가?

 

LPCTSTR을 좀 정리하자

LPCTSTR은 비슷한게 많다.

일단 윈도우에서 사용되고, 그 목적은 유니코드 기반으로 개발을 하기 위해 자동 캐스팅을 돕는 장치였다.

그래서 가장 기본형은 LPSTR이다 이건 char * 형식이다. 그리고 const를 선언한 LPCTSTR도 있다.