문자열 타입 변환 | C & C++

Program C/C++ 2010. 10. 13. 12:53 Posted by HisPark


이 문서에서는 문자셋 간의 변환에 대해서 살펴보도록 하겠습니다. 문자열 타입 변환 이라하면 단순히 char와 wchar_t간의 변환이라고 생각하기 쉬운데 문자셋이란 개념을 꼭 생각하셔야 합니다.


잊지 말아야 할 것
아래 적어둔 것은 자주 헷갈리는 것입니다. 먼저 아래것들을 확실히 해둡시다.

MBCS는 한가지 인코딩이 아니다.
MBCS를 담는 자료형은 char다
UTF-8도 MBCS와 같은 char에 저장한다.
Wide Char는 한가지 인코딩 방식이다. (컴파일러에 따라 UTF-16이거나 UTF-32)
Wide Char는 wchar_t에 담는다.
다시 말하면 wchar_t에는 UTF-16이나 UTF-32로 인코딩 된 문자열만 담긴다.

결국 데이타 타입 관점에서 보면 아래 세가지 케이스가 있다고 할 수 있습니다.

char -> wchar_t
char에 어떤것이 들어 있는지 말해주어야 합니다. 한글이 들어있는 CP949 일수도 있고 일본어가 들어 있는 CP932 등일수도 있습니다. 또 UTF-8일 수도 있습니다.

wchar_t -> char
wide_char를 어떤 인코딩으로 바꿔서 char에 넣을지 정해 주어야 합니다. 역시 위와 마찬가지로 UTF-8, CP949, CP932, 기타 등등이 가능합니다.

char -> char
이런 경우는 두 가지 밖에 없습니다. MBCS를 UTF-8로 바꾸거나 반대의 경우거나.
왜 이 두 가지인지는 MBCS에대해 이해하고 있다면 당연하게 여기실 겁니다.



외부 라이브러리를 이용하는 방법
가장 믿음직하고 다양한 변환이 가능하지만 외부 라이브러리를 설치해야 한다는 압박이 있습니다. 대표적으로 iconv가 있는데 기본적으로 dll을 사용하는 방식으로 되어 있습니다. qt의 qstring을 이용하는 방법도 있다고 하는데 설치하는게 만만찬다고 합니다. 약간의 트릭으로 boost 라이브러리를 사용하는 방법이 있는데 관련 내용은 CPP.log 님의 블로그를 참고하세요. 리눅스에서 iconv없이 개발하고자 할 때 유용할 것 같습니다.



codecvt이용한 변환
쉽지는 않지만 STL의 codecvt를 이용하시면 standard한 인코딩 컨버전 함수를 만들 수 있습니다. 이 방법의 장점은 standard라는데 있습니다. 모든 OS에서 컴파일이 가능합니다. 하지만 실행시점에서 모든 OS가 정상적인 동작을 보장하지 않는 다는 치명적인 단점이 있습니다.
아래는 CPP.log 님의 블로그 에서 가져온 소스입니다. 블로그로 가시면 반대로 변환하는 함수도 보실 수 있습니다. std::locale에 원하는 인코딩을 정해 주실 수 있습니다. 이 때 위에 잊지 말아야 할 것에서 1번을 기억하셔야 합니다. MBCS는 한가지 인코딩이 아니기 때문에 변환할 인코딩을 선택해 주어야 합니다. 아무것도 안적으면 시스템에서 사용하는 문자셋이 설정됩니다.


std::string wcs_to_mbs(std::wstring const& str, std::locale const& loc = std::locale())
{
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_t;
    codecvt_t const& codecvt = std::use_facet<codecvt_t>(loc);
    std::mbstate_t state = std::mbstate_t();
    std::vector<char> buf((str.size() + 1) * codecvt.max_length());
    wchar_t const* in_next = str.c_str();
    char* out_next = &buf[0];
    std::codecvt_base::result r = codecvt.out(state,
        str.c_str(), str.c_str() + str.size(), in_next,
        &buf[0], &buf[0] + buf.size(), out_next);
    if (r == std::codecvt_base::error)
       throw std::runtime_error("can't convert wstring to string");  
    return std::string(&buf[0]);
}



만약 위의 함수에 locale을 "ko_KR.UTF-8"로 설정하면 wstring을 UTF-8로 변환하실 수 있습니다. 하지만 locale은 OS의 특성을 탑니다. OS별로 동작 할 수도 안할 수도 있다는 이야기 입니다. 리눅스 계열의 몇몇 OS에서는 잘 돌겠지만 Windows 에서는 확실히 동작하지 않습니다. 해결방법은 외부 라이브러리를 사용하거나 직접 구현하는 수밖에 없습니다.



MultiByteToWideChar를 이용한 변환
MultiByteToWideChar()함수와 WideCharToMuliByte()함수를 사용해서 인코딩을 변환해 주는 것도 가능합니다. 이 두함수는 윈도우에서만 지원합니다. 따라서 OS 디펜던시가 없는 라이브러라 같은 것을 개발하신다면 이 두함수는 사용하실 수 없긴 하지만 많은 윈도우 개발자들이 사랑하는 함수죠.

먼저 MBCS를 UTF-16으로 바꾸는 함수입니다.


int MultiByteToWideChar(
  UINT CodePage,         // 원본 스트링의 현재 인코딩 상태
  DWORD dwFlags,         // 0을 쓰면 된다.
  LPCSTR lpMultiByteStr, // 변환하려는 문자열
  int cbMultiByte,       // -1을 넣으면 lpMultiByteStr의 길이를 알아서 계산
  LPWSTR lpWideCharStr,  // 변환된 유니코드를 저장할 공간
  int cchWideChar        // 유니코드를 저장할 공간의 사이즈
);

첫번째 인자에는 CP_ACP나 UTF-8둘중 하나를 넣으시면 됩니다.

CP_ACP : 시스템에서 사용하는 MBCS -> UTF-16
UTF-8 : UTF-8 -> UTF16
기타 코드 페이지
이함수는 특이한 기능이 있는데 맨마지막 인자에 0을 넣으면 필요한 wchar_t의 공간을 리턴합니다. 이를 이용해서 변환된 문자열에 딱맞는 공간을 할당 하실 수 있습니다. 다음은 반대로 UTF-16을 MBCS로 바꾸는 함수입니다.


int WideCharToMultiByte(
  UINT CodePage,           // 변환 타겟 인코딩
  DWORD dwFlags,           // 0
  LPCWSTR lpWideCharStr,   // 원본 스트링
  int cchWideChar,         // -1을 넣으면 원본 스트링 길이가 자동할당
  LPSTR lpMultiByteStr,    // 목적지
  int cbMultiByte,         // 목적지 사이즈
  LPCSTR lpDefaultChar,    // 실패시 사용
  LPBOOL lpUsedDefaultChar // 실패 여부 판단
);

MultiByteToWideChar()함수와 거의 같은데 인자가 두개더 있습니다. lpDefaultChar인자는 인코딩이 실패했을 때 실패한 문자열 대신 사용될 문자열입니다. 보통 NULL로 지정하시면 됩니다. 마지막 인자 lpUsedDefaultChar는 변환에 실패한 문자가 하나라도 있을 때 TRUE를 리턴합니다. 유니코드는 MBCS보다 사이즈가 큰 집합이기 떄문에 MBCS에서 유니코드로의 변환은 실패할 일이 없지만 반대는 실패할 수도 있음을 기억하셔야 합니다.



USE_CONVERSION 를 이용해 변환
이 매크로를 이용하면 아주 쉽게 문자열을 변환할 수 있습니다. 사용법은 아래와 같습니다.


#include <atlconv.h> // 필요 include
#pragma comment(lib, "atls.lib") // 필요 lib


void SomeFunction()
{
    USES_CONVERSION; // 먼저 적으셔야 합니다.
    
    CAtlStringW wideStr[]=L"abc가나다";
    CAtlStringA ansiStr;
    ansiStr = W2A(widechar);  // UTF-16을 MBCS로 바꿉니다.
}

W2A말고도 여러 매크로를 제공하는데요. 아래와 같습니다. (const 변환용 매크로 제외)
매크로 설명
A2W LPCSTR -> LPWSTR
W2A LPCWSTR -> LPSTR
A2T LPCSTR -> LPTSTR
T2A LPCTSTR -> LPSTR
T2OLE LPCTSTR -> LPOLESTR
OLE2T LPCOLESTR -> LPCSTR

이 매크로는 사용하기엔 편하지만 UTF8을 UTF-16 으로 변환하는데 쓰실 수 없습니다. 또 주의 해야 할 것이 있는데요. 각 매크로를 사용할 떄 변환에 필요한 공간이 스택에 할당된다는 점입니다. 따라서 실제적으로 아래를 주의 하셔야 합니다.

너무 큰 스트링을 변환하고 시도하면 스택 오버 플로가 날 수 있습니다.
루프 안에서 과도한 변환을 시도하면 스택 오버 플로가 날 수 있습니다.
스택에서 잡힌 공간이기 때문에 매크로의 결과를 리턴시키면 안됩니다.



CA2W계열의 클래스를 이용한 변환
USE_CONVERSION 를 이용한 변환이 편하긴 한데 위에서 말한대로 문제가 좀 있기 때문에 ATL 7.0이후 CATW계열의 클래스들이 새로 등장했습니다. 이 아이는 클래스인데다가 기본적으로 128바이트의 내부 버퍼를 가지고 있고(변경 가능 합니다만..), 이보다 크면 알아서 힙에 동적할당을 해주는 똑똑한 놈입니다.


#include <atlconv.h> // 필요 include
#pragma comment(lib, "atls.lib") // 필요 lib


void SomeFunction()
{
        CA2W p ("123가나다");    // 클래스 선언
        CA2WEX<256> p2 ("123마바사");    // EX를 붙이면 내부 버퍼 사이즈를
                                         // 변경시킬 수 있습니다만 새로운
                                         // 사이즈별로 코드가 새로 생깁니다.


        CAtlStringW str(p);      // p가 LPWSTR을 리턴합니다.
        CAtlStringW str2 = p2;   // p2가 LPWSTR을 리턴합니다.
}

이런식으로 사용합니다. 주의할 점은 역시 위 코드에서 보이는 p를 리턴 시키면 안됩니다.(당연한 이야기지만) CA2W외 그밖의 클래스 들은 아래와 같습니다. (아래 목록은 cosnt와 버퍼 사이즈를 변경할 수 있는 EX가 붙어 있는 것들이 제외 되었습니다.)
클래스 설명
CA2W LPCSTR -> LPWSTR
CW2A LPCWSTR -> LPSTR
CA2T LPCSTR -> LPTSTR
CT2A LPCTSTR -> LPSTR
CT2OLE LPCTSTR -> LPOLESTR
COLE2T LPCOLESTR -> LPCSTR
아쉬운 것이 있다면 UTF-8로의 변환을 지원하는 클래스가 있었다면 좀더 좋았을 텐데 이점이 조금 아쉽습니다.


참조 사이트: http://killrain.net
[출처] 문자열 타입 변환 (차니의 컴퓨터 마을) |작성자 newchany

'Program C/C++' 카테고리의 다른 글

Class 내부 Thread basic...  (1) 2013.05.10
typedef...  (0) 2011.09.26
c 표준 함수들  (0) 2011.06.24
C++의 다양한 string 타입 | C &amp; C++  (0) 2010.01.30
About String  (0) 2005.08.05