Tech & IT/프로그래밍

Win32 문자열

해피콧 2008. 12. 11. 15:00
'); }
'); }
출처 : http://blog.naver.com/seunghyni/110014783397

“ Win32 문자열 ”
ColdFloor  |  2006/09/30 20:09  |  개발/Etc
C++ 문법을 배우고 Win32 프로그래밍을 시작 할 때,
가장 어려운 것중 하나는 엄청난 수의 Win32 DataType들과 생소한 Macro들이었다.

생전 처음 보는 그 녀석들을 이해하려면 상당한 범위를 돌아다녀야 했고,  
이해하기 보다는 대충 이렇게 쓰면 오류없이 컴파일 되더라 정도로 사용하는게 현실이었다.

그 중에서도 문자열부분은 꽤나 두통을 유발하는 것들인데 유니코드고 멀티바이트고 신경안쓰고 대충 만들고 싶어도 어디에서건 꼭 발목을 잡히게 된다.
그렇다고 아주 해결이 안되는것도 아닌데, 그 때 그때 급하게 땜질식으로 코딩을 하다보니
문자열관련 부분에 대해서는 계속 골치가 아플수밖에 없었다.

싱글바이트캐릭터(SBCS : Single-byte character set),
멀티바이트캐릭터(MBCS : Multi-byte character set),
유니코드 ( Unicode characters ), 와이드 캐릭터 ( Wide characters )로 시작한 문자코드들부터 시작해서
wchar_t, char, TCHAR, BSTR, _bstr_t 등의 관련 데이터 타입들.. L, _T()의 매크로들
거기에 기반한 LPSTR, LPCSTR, LPWSTR, LPTSTR, LPCTSTR등의 데이터 타입들에 대해 간략히 보자


지금 정리하는 내용들은 기본적으로 코드프로젝트의 아티클에 대한 정리 요약본이다.
원본 아티클 링크는 다음과 같다.
The Complete Guide to C++ Strings, Part I - Win32 Character Encodings
The Complete Guide to C++ Strings, Part II - String Wrapper Classes

문자 Encoding 방식

싱글바이트캐릭터
SBCS (Single-byte character set)
한 문자 표현에 1Byte를 사용하는 방식이다.
예로 ASCII 코드가 있으며, 한글이나 일본어표현은 불가능하다.


멀티바이트캐릭터
MBCS (Multi-byte character set)
한 문자 표현에 1Byte 이상을 사용하는 방식으로 Windows에서 MBCS에는 딱 두종류가 있다.
SBCS와 DBCS( Double-byte character set) 결국 많아봐야 최대 2Byte라는 얘기다.
한글이나 일본어가 처리되는 기본 방식이다.
즉,

printf( “안녕하삼” );
std::cout<<”배고프네”;
 

  이렇게 코드를 작성하고 컴파일해서 실행했을 때, 이게 바로 MBCS를 사용한 것이 된다.


유니코드
Unicode (wide characters)
유니코드는 모든캐릭터들을 2Byte로 표현하는 표준 Encoding 방식이다.
즉 구조적으로 한 글자가 1Byte, 2Byte, 3Byte 어느것이든 가능한 MBSC와 구분된다.


> C++에서 종료 문자열
SBCS/MBCS
코드상 별도의 구분없이 사용하므로, 기본 C-Style 문자열에서 처럼 ‘\0’ 한번
즉, 0Byte 값이 문자열의 끝을 표현.

Unicode
모든 캐릭터를 2Byte로 Encoding 하므로, 종료 문자열도 ‘\0’이 두번 위치.
즉, 0Byte 2개가 문자열의 끝을 표현.



> Data type
SBCS/MBCS
char : 일반적인 1Byte 문자형 char를 사용해서 표현

Unicode
wchar_t : wide-character 타입 문자형을 사용하며, 값 지정시 prefix L 을 사용한다.
wchar_t wch = L’즐’; //2Bytes
wchar_t* wstr = L”Hi”; // 6Bytes



> 문자열 처리 함수
SBCS
strcpy(), sprintf(), atol()등의 함수

MBCS
_mbscpy()처럼 _mbsXXX()식으로 이름이 붙은 mbcs전용 함수를 사용해야 한다.

Unicode
wcsXXX()식의 함수나 swprintf(), _wtol()처럼 앞에 w등이 붙은 unicode전용 함수를 사용해야 한다.



Win32 API에서 MBCS와 Unicode사용
windows에서는 두종류의 api를 제공한다.
SetWindowTextA() / SetWindowTextW()처럼 ~A()함수는 MBCS용 그리고 ~W()함수는 Unicode용이다.
실제로 코딩 할 때에는 직접 문자열 Encoding에 맞추어 호출하지 않고 Macro를 사용해서 전처리기에 따라 처리를 하게 된다.

즉,


#ifdef UNICODE

   #define SetWindowText SetWindowTextW
#else
   #define SetWindowText SetWindowTextA
#endif



이런식이다.

문제는 이렇게 전처리기를 통해 컴파일시에 적용되기 때문에 발생하는데,
실제로는 코드에 사용된 문자값들을 지정하는 코드들까지 바뀌어야 한다는 것이다.
HWND hwnd = GetSomeWindowHandle();

#ifdef UNICODE

   wchar_t szNewText[] = L"we love Bob!";
#else
   char szNewText[] = "we love Bob!";
#endif

  SetWindowText ( hwnd, szNewText );


이렇게 리터럴값을 지정할 때에도 Unicode경우 L prefix가 붙기 때문에 상당히 귀찮은 작업이 된다.
그리고 결국 이것을 해결하기 위해 몇가지가 더 추가될 수 밖에 없었던 것 같다.

그래서 해결책으로 나온것이 TCHAR 님이시다.
#ifdef UNICODE
   typedef wchar_t TCHAR;
#else
   typedef char TCHAR;
#endif

#ifdef UNICODE
   #define _T(x) L##x
#else
   #define _T(x) x
#endif

이렇게 매크로 되어있기 때문에 TCHAR 타입을 사용하면, 상황에 맞게 wchar_t / char로 구분되어 처리되고,
prefix L의 문제도 _T()를 사용해서 해결이 가능한것이다.
즉, TCHAR과 _T() 매크로를 사용해서 코드를 작성하면, 컴파일러의 옵션변경만으로 MBCS/Unicode 컴파일이 가능해진다.

거기에 더해서 문자열 처리 함수들의 경우들도 strrchr() / _mbsrchr() / wcsrchr()등으로 구분해서 사용하던 것을
_tcsrchr()식으로 _t가 붙은 함수들로 대체 할 수 있다.

이 함수 매크로들은 TCHAR.H에 정의 되어있는데,
http://msdn2.microsoft.com/en-us/library/tsbaswba.aspx 여기에서 확인가능하다.

즉, TCHAR은 Windows에서 MBCS/Unicode 호환코드를 작성하기 위해 사용하는것이다.

또 일반적으로 사용되는 문자열 관련 데이터타입은 다음과 같다.
WCHAR : wchar_t
LPSTR : char*
LPCSTR : const char*
LPWSTR : wchar_t*
LPCWSTR : const wchar_t*
LPTSTR : TCHAR*
LPCTSTR : const TCHAR*

..알고보면 아무것도 아니다; 더 해서.
OLECHAR : Unicode 문자 wchar_t
LPOLESTR : OLECHAR*
LPCOLESTR : const OLECHAR*

Prefix 경우에는
_T(x) : Unicode빌드일 때, L
OLESTR(x) : LPOLESTR 문자열을 지정하기위해 L
TEXT(x), _TEXT(x), __T(x) : _T(x)와 동일

COM/ATL/STL - BSTR과 VARIANT, string, CComBSTR
COM 관련 코드를 작성하려고 보면 여기서만 쓰이는 생소한 데이터 형들이 등장하는데, 그중 문자열관련해서 다음과 같은 것을
볼 수 있다.


BSTR
Pascal-Style(길이값 내장)과 C-Style(널종료문자)을 섞어 놓은 형식으로 기본 구조는 다음과 같다.
-    4Byte(길이정수) + 문자값(2Byte) + 종료문자( 0 2개 )
즉, 최초에 DWORD의 정수데이터가 붙고 그 뒤로 Unicode식의 2Byte Encoding 문자열이 붙는형식이다.
그러나 이 앞쪽의 정수 부분은 C++ 코드 작성시에는 없다고 생각해야 한다.
왜냐하면, typedef OLECHAR* BSTR; 로 선언 되어 있기 때문.
COM을 통해 데이터가 전송될 때 알아서 마샬링 되는 것 같다.
아무튼 실제로 BSTR은 WCHAR과 다름에도 불구하고 내부적으로 같은 형식으로 인식되기 때문에 주의를 해야한다.
함수에 인자로 넘길경우 컴파일 오류가 안지 않더라도 내부적으로 오류가 발생하기 쉽다.

게다가 BSTR 은 COM 라이브러리를 통해 마샬링 되어야 하기 때문에 메모리 관리를 다른 곳에서 해야한다.
즉, 사용할 때 메모리 할당과 해제를 명시적으로 API를 통해서 해야한다.
SysAllocString() : 메모리 할당시 사용
SysFreeString() : 메모리 해제시 사용
BSTR bstr = NULL;

bstr = SysAllocString ( L"Hi Bob!" );

if ( NULL == bstr )
  // out of memory error

// Use bstr here...

SysFreeString ( bstr );

이렇게 사용한다.


_bstr_t
BSTR데이터형을 사용하는게 귀찮은 사람들을 위한 BSTR Wrapper 클래스다.
BSTR대신 함수에 넘겨줄수는 없다 ? 가능하지만 상당히 제약적이다.
직접 내부 BSTR에 접근이 안돼기 때문에 함수에 BSTR대신 넘겨주기 위해서는 ATL에서 지원하는 CComBSTR클래스를
사용하면 된다.
다음과 같이 생성 및 문자열 encoding 변환을 할 수 있다.
// Constructing
_bstr_t bs1 = "char string"; & // construct from a LPCSTR
_bstr_t bs2 = L"wide char string"// construct from a LPCWSTR
//내부적으로 2Byte Unicode 형식이지만, char / wchar_t 양쪽에서 생성 할 수 있다.

_bstr_t bs3 = bs1;  // copy from another _bstr_t
_variant_t v = "Bob";
_bstr_t bs4 = v;  // construct from a _variant_t that has a string

// Extracting data
LPCSTR psz1 = bs1;  // automatically converts to MBCS string
LPCSTR psz2 = (LPCSTR) bs1;  // cast OK, same as previous line
LPCWSTR pwsz1 = bs1;  // returns the internal Unicode string
LPCWSTR pwsz2 = (LPCWSTR) bs1; // cast OK, same as previous line 변환된다!!
BSTR  bstr = bs1.copy();  // copies bs1, returns it as a BSTR

// ...
SysFreeString ( bstr )// 수동적으로 메모리를 해제해줘야 한다.




_variant_t
COM에서 사용되는 VARIANT의 wrapper 클래스다.
당연히 BSTR과 _bstr_t의 관계처럼 좀더 사용하기 용이하다.
기본적으로 VARIANT는 문자열외에 다양한 데이터를 저장하기 위한 구조체지만, 문자열을 저장할 경우 BSTR형식으로
저장이 된다.
_bstr_t와는 다르게 _variant_t는 VARIANT를 상속받은 클래스로 모든 함수에 VARIANT 대신 넘겨주는 것이 가능하다.
내부의 VARIANT는 감춰져있다.
// Constructing
_variant_t v1 = "char string";  // construct from a LPCSTR
_variant_t v2 = L"wide char string"// construct from a LPCWSTR
_bstr_t bs1 = "Bob";
_variant_t v3 = bs1;  // copy from a _bstr_t object

// Extracting data
_bstr_t bs2 = v1;  // extract BSTR from the VARIANT
_bstr_t bs3 = (_bstr_t) v1; // cast OK, same as previous line
위와 같이 _variant_t와 _bstr_t 사이에 전환이 용이 하다.



basic_string::string / basic_string::wstring
STL의 문자열 클래스 basic_string에는 MBSC/Unicode용으로 각각 string/ wstring이 존재 한다.
string은 char / wstring은 wchar_t를 저장하는데, TCHAR은 존재하지않는다.
TCHAR과 STL을 함께 사용하려면 간단히 다음처럼 직접 만들어주면된다.
// Specializations
typedef basic_string<TCHAR> tstring; // string of TCHARs 새로 정의 해준다.

// Constructing 이렇게 각각 생성할 수 있다.
string str = "char string";  // construct from a LPCSTR
wstring wstr = L"wide char string"// construct from a LPCWSTR
tstring tstr = _T("TCHAR string")// construct from a LPCTSTR

// Extracting data
// 값을 사용할 때에는 .c_str()을 통해 해당하는 원 데이터형으로 반환된다.
LPCSTR psz = str.c_str();  // read-only pointer to str's buffer
LPCWSTR pwsz = wstr.c_str()// read-only pointer to wstr's buffer
LPCTSTR ptsz = tstr.c_str()// read-only pointer to tstr's buffer
_bstr_t에 바로 할당하기 위해서는
_bstr_t bs1 = wstr.c_str(); 와 같은 식으로 내부 데이터 값을 받아오면 된다.



CComBSTR
ATL의 BSTR wrapper 클래스로 _bstr_t보다 좀더 유용한 기능들이 있다.
우선 COM함수에 BSTR대신 넘겨줄 수 있고, BSTR 메모리 관리를 자동으로 해준다.
내부에 MBCS 변환 기능은 없다.
문자열 변환에는 ATL 변환 Macro를 사용하면 된다. <- 매우 편리하다
// Constructing
CComBSTR bs1 = "char string";  // construct from a LPCSTR
CComBSTR bs2 = L"wide char string"// construct from a LPCWSTR
CComBSTR bs3 = bs1;  // copy from another CComBSTR
CComBSTR bs4;

bs4.LoadString ( IDS_SOME_STR )// load string from string table

// Extracting data
BSTR bstr1 = bs1;  // returns internal BSTR, but don't modify it!
BSTR bstr2 = (BSTR) bs1; // cast ok, same as previous line
BSTR bstr3 = bs1.Copy()// copies bs1, returns it as a BSTR
BSTR bstr4;

// CComBSTR의 메모리 관리를 꺼버릴 수 있다.
bstr4 = bs1.Detach()// bs1 no longer manages its BSTR, 메모리 관리는 수동으로 해야한다.

// ...
SysFreeString ( bstr3 );
SysFreeString ( bstr4 );


추가로, 연산자&는 내부의 BSTR*를 리턴하도록 오버로딩 되어있기 때문에 사용상 고려할점이 존재한다.
STL의 list같은 컬렉션에서 CComBSTR을 사용하기 위해서는 &연산자 오버로딩으로 일반 데이터타입과는 다르게,
CAdapt 를 사용해야 한다. 즉 다음과 같다.
std::list< CAdapt<CComBSTR> > bstr_list;



CComVariant
ATL의 VARIANT wrapper 클래스다. _variant_t와는 다르게 내부의 VARIANT가 감춰져 있지 않아서 직접 접근이 가능.
게다가 CComBSTR과 간단히 형 변환되지는 않기 때문에 값을 넣기 위해서는 검사를 해야한다.
CComVariant v4 = ... // Init v4 from somewhere
CComBSTR bs3; 
// 검사하고 변환이 가능하면 넣는다.
if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) ))
bs3 = v4.bstrVal;



ATL Conversion Macros ? MBCS / Unicode / BSTR 간의 Encoding 변환
Macro 함수의 이름은 다음과 같은 구조로 돼어있다.
[원본 타입]2[새 타입] / [원본타입]2C[새 타입]
2는 그냥 변환 / 2C는 constant pointer를 말하는 C이다.

A : MBCS 문자열 char*
W : Unicode 문자열 wchar_t*
T : TCHAR 문자열 TCHAR*
OLE : OLECHAR 문자열 OLECHAR*
BSTR : BSTR

예를 들어, W2A() 매크로는 Unicode문자열을 MBCS문자열로 변환한다.
매크로를 사용하기 위해서는 atlconv.h 헤더를 포함해야 하는데, 해당 헤더파일만 포함하면 굳이 ATL프로젝트가 아니라도
사용이 가능하다.

사용시에는 우선 USES_CONVERSION 이라는 매크로를 사용전에 호출해주어 변환에 필요한 기본 변수들을 정의 한뒤,
실제 변환 매크로를 사용하면 된다.
// Functions taking various strings:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// Functions returning strings:
void Baz ( BSTR* pbstr );

#include &lt;atlconv.h&gt;

main()
{
using std::string;
USES_CONVERSION;  // declare locals used by the ATL macros

// Example 1: Send an MBCS string to Foo()
LPCSTR psz1 = "Bob";
string str1 = "Bob";

Foo ( A2CW(psz1) );
Foo ( A2CW(str1.c_str()) );

// Example 2: Send a MBCS and Unicode string to Bar()
LPCSTR psz2 = "Bob";
LPCWSTR wsz = L"Bob";
BSTR bs1;
CComBSTR bs2;

bs1 = A2BSTR(psz2);  // create a BSTR
bs2.Attach ( W2BSTR(wsz) )// ditto, assign to a CComBSTR

Bar ( bs1 );
Bar ( bs2 );

SysFreeString ( bs1 );  // free bs1 memory
// No need to free bs2 since CComBSTR will do it for us.

// Example 3: Convert the BSTR returned by Baz()
BSTR bs3 = NULL;
string str2;

Baz ( &bs3 )// Baz() fills in bs3

str2 = W2CA(bs3)// convert to an MBCS string
SysFreeString ( bs3 )// free bs3 memory
}