2001-11-15 bro(bro@shinbiro.com)
MSDN Unicode 관련 설명 해석(비완성-_-) 조경민
==========================================================================

Article 3. Strings the OLE Way
Bruce McKinney
April 18, 1996

소개글

비베 문자열과 VC++의 문자열의 차이점은 "내가 그걸 해주겠다"와 "너가 해라"이다.

바이트들의 배열을 관리하려 할때 길이를 관리하는 두가지 방법이 있다. 마커시스템
(marker system)은 배열의 끝에 특수한 마크를 둔다. 모든것이 이 마커의 유효함에
달려 있다. 카운트 시스템(count system)은 특정 배열 슬롯안에 원소들의 갯수를
포함하게 된다. 배열의 크기가 조정되면 그떄마다 갱신되어야 한다. 둘다 장점,단점이
있다. 마커시스템에선 마커로 쓰이는 특정 값이 배열내에 존재해선 안된다. 카운트
시스템은 정확한 길이를 기억하기 위해서 세심한 관리가 필요하다.

C언어와 그와 비슷한 것들은 문자열을 기억하기 위해 null 문자를 마커로 쓰는
마커시스템을 사용한다. 그 외의 모든 언어들은 카운트 시스템을 이용한다. 다수결
의 원칙으로 보아 그것이 더 나은 선택이라고 생각할지 모르나, 실제로는 C는 여전히
'최후의 웃는자'로 남아 있다. 많은 유명한 운영체제( 유닉스, 윈도우, OS/2 ) 시스템은
null로 끝나는 문자열을 받게끔 되어 있다. 그 결과, 파스칼, 포트란같은 언어들은
시스템에 null로 끝나는 문자열을 주기 위해 특별히 null로 끝나는 문자열을 제공하고
있다. 베이직은 null로 끝나는 문자열을 위한 특별한 타입은 갖고 있지 않지만,
null로 끝나는 문자열을 만들기 쉽게 하는 기능을 갖고 있다.

언어 독립적인 OLE는 한쪽 언어에 치우치면 안되었다. OLE에 있어 null이 특수한 문자는
아니지만 대상 호스트 운영체제를 위해 null로 끝나는 문자열을 결과물로 보여줄 수 있어야
한다. 더 중요하게 OLE는 프로세스, 머신, 때로는 인터넷 상에서 전송되어야 하는
안정적이며 신뢰감을 갖는 미래지향적 문자열을 지원해야 한다. 난 BSTR은 Basic STRing
을 축약한 것이라고 말한적이 있다. 그러나 사실 BSTR은 베이직의 문자열 보단 파스칼의
문자열에 더 가깝다.

어떤 경우든, C++프로그래머들에겐 OLE를 위해 문자열을 쓸때 배워보지못한 것을 접하게
된다. 그러나 BSTR의 상세 내용을 알기 전에, 너는 유니코드와 안시문자열의 차이점을
명백하게 이해하고 있어야한다.

Unicode vs. ANSI

문자열체계에서 우리는 아주 흥미로운 시대에 살고 있다. 세상을 이끄는 마이크로소프트
(그리고 다른 국제적인 회사들)는 ANSI에서 Unicode 문자들로 바꾸고 있다. 그러나
그 변화는 그리 평탄하지 않다.

많은 유니코드에 대한 론란은 우리들이 문자들이 표현되는 커다란 변화의 가운에에 있다는
데에서 기인한다. 예전 방식은 256바이트들의 ANSI 문자열 셋을 이용했고, 다른 nnn-ANSI
문자 셋을 표현하기 위해서 두개의 바이트(double-byte)를 갖는 문자들을 예약해 두었다.
이는 라틴문자를 쓰는 문화적 제국주의자들에게는 매우 효율적이다. 그러나 큰 문자 셋들을
쓰는(비 영어권나라) 사람들에겐 비효율적이다. 유니코드는 모든 문자를 두바이트로 표현한다.
이는 문화적인 제국주의자들에게 비효율적이다. ( 비록 그들은 상위 바이트는 0으로 채워지고
그대로 쓰던 128개의 문자를 하위 바이트로 갖는 영광을 갖는다. ) 그러나 이는 다른 나라
에서 더 효율적이다.

Different Views of Unicode
유니코드에 대한 다른 시점

때때로, 모두들 유니코드를 사용할것이지만, 아무도 변환에 드는 비용을 생각해보지
않는것 같다.

* Windows 3.x - 유니코드를 전혀 모른다. 그리고 후에도 모를것이다.
* 16-bit OLE - 위와 같다.
* Windows NT - 첫째로 유니코드를 쓰며, 다음으로 호환을 위해 ANSI를 지원한다.
               모든 문자열은 내부적으로 Unicode이지만 NT 또한 런타임에서
               내부 Unicode문자열을 ANSI 문자열로 변환해 줌으로 완벽히
               ASNI를 제공한다. NT 프로그램은 빈번한 문자열 변환을 피하기 위해
               비록 유니코드가 두배의 데이타 길이를 갖더라도 더 효율적이게
               직접 Unicode문자열들을 사용한다.

* windows 95 - 내부적으로 ANSI 문자열을 사용한다. 그러므로 간접적으로도 유니코드
               문자열은 한가지 특수한 큰 경우를 제외하곤 지원되지 않는다.

* 32-bit OLE - 유니코드를 지원하게 만들어졌으며 ANSI를 쓰지 않았다. OLE의 문자열
               타입인 OLESTR과 BSTR은 유니코드이다. 어떤 32비트 운영체제이든
               OLE를 쓰길 원한다면 적어도 부분적으로라도 유니코드를 제공해줘야한다.
               윈도우즈 95는 OLE 작업을 하기 위한 유니코드 지원정도는 충분하다.

* Visual Basic - 설계자는 내부적인 문자열 구현에 대해 어떤 거친 결심 했었다. 그들은
               ANSI가 윈도우즈95와 NT의 공통된 부분이기에 ANSI를 선택했었을 것이다.
               그리고 OLE를 작동하기 위해 항상 유니코드로 컨버트 했었을 것이다.
               그러나 Visual Basic 4.0이후로 OLE는 전반적인 기술이 되었고,
               윈도우즈 95와 비호환성을 잠재적으로 갖지만 유니코드는 내부 기본
               포맷이 되었다. 유니코드 선택은 비주얼 베이직 개발자나 비주얼 베이직을
               만든 개발자에게 많은 문제와 비효율을 야기시켰다.

* 현실 - 현재 존재하는 대부분의 파일은 ANSI를 사용한다. .WKS, .DOC .BAS .TXT나 대부분
         다른 표준적인 파일 포맷은 ANSI를 이용한다. 만일 시스템이 유니코드를 내부적
         으로 쓰게되고, 그런 데이타 포맷을 읽거나 쓰길 원한다면, 시스템은 반드시
         Unicode-to-ANSI 변환을 제공해주어야 한다. 언젠가는 유니코드데이타 파일 포맷이
         생기겠지만, 그러나 오늘날엔 아직 그런 포맷이 드물다.

당신에게 의미하는것은 무엇일까? 이는 당신이 작성하는 어떤 프로그램이든 다음의 선택을
하라는 것을 의미한다.

- 만일 내부적으로 유니코드를 이용해 작성한다면, 당신의 애플리케이션은 오직 Windows NT
하에서만 빠르게 실행이 가능할것이다. 만일 당신이 ANSI를 사용하는 표준 파일 포맷에
문자열 데이타를 쓸 일이 없다면 내외부적으로 유니코드만 사용하면 문자열 변환이 필요없다.
이렇게 짜여진 애플리케이션은 windows 9x가 유니코드를 지원하는 어느날부터는 사용이
가능해질것이다.
- 만일 당신이 내부적으로 ANSI로 작성했다면, 당신의 애플리케이션은 Windows NT나 WIndows
  95에서 동작 가능할 것이다. 그러나 이는 Windows NT하에서는 많은 문자열 변환이 일어나
  느리게 동작 할 섯이다. 이런 애플리케이션은 세상 전체가 유니코드만 사용하는 언젠가
  사용할수 없을 것이다. 그러나 아마도 그런 날은 당신이 살아있는 동안은 일어나지 않을것이다.

대부분 개발자들의 명백한 선택은 ANSI버전을 사용하는 것이다. 이는 모든 32비트 Windows
플랫폼에서 잘 동작하기 때문이다. 그러나 난 두개의 버전을 빌드하길 바란다.

만일 당신이 당신의 애플리케이션을 ANSI와 Unicode둘다 쓰게끔 한다면, Win32와 C런타임
라이브러리 둘은 같은 소스로 호환 가능한 프로그램을 십게 만들수있게 다양한 타입과 매크로
들을 지원할 것이다. 이를 이용해 _UNICODE 심볼을 빌드시 정의하면 유니코드로, _MBCS를
정의해 ANSI로 빌드할 수 잇다. 예는 이미 비주얼 스튜디오의 세팅안에 있다.

알아둘점 이 기사에 언급되는 것엔 double-byte 스트링(DBCS)와 multi-byte 스트링(MBCS)의
차이점이 없다. 유사하게 "wide character"와 "Unicode"는 이 기사에선 동의어로 본다.

A WHCAR Is a wchar_t Is an OLECHAR
WCHAR는 wchar_t이고 wchar_t는 OLECHAR이다.( 같은 놈이다.)

Win32 API에서 ANSI는 MBCS를 의미한다. Win32 문자열 함수들 (lstrlenA,lstrcpyA등)은
multi-byte 스트링으로 간주된다. Unicode version은 (lstrlenW, lstrcpyW)로 얻을 수 있다.
불행히도, Windows 95에선 구현이 안되어있다. 그래서 당신은 BSTR을 그 함수들과 사용할수
없다. 마지막으로 당신은 일반적인 매크로 버전 (lstrle, lstrcpy)로 당신이 UNICODE심볼을
정의했는가 안했는가로 선택적으로 구현할 수 있다.

C++ 런타임 라이브러리는 좀더 유연하다. 각 문자열 함수들은 single-byte 함수(strlen)을
지원한다. 또한 multi-byte 함수(_mbslen)을 지원하며 wide character(wcslen)을 지원하며
매크로 버전인 (_tcslen)을 제공하여 _UNICODE, _MBCS, 또는 _SBCS 심볼을 정의했는가로
선택하게 할 수 있다. 이런 것들은 OLETYPE.H에 정의되어있다.

Win32는 MultiByteToWideChar와 WideCharToMultiByte 함수를 ANSI와 Unicode 변환을 위해
제공한다. C++런타임 라이브러리는 mbstowscs와 wcstombs 함수를 같은 목적으로 제공한다.
Win32 함수는 좀더 유연하다. 그러나 이 기사에선 언급하지 않는다. 우리는 간단한 런타임
버전만 사용할 것이다.

Unicode와 ANSI버전에 따라 타입들이 존재하며 OLE는 그 자신의 타입을 Win32와 ANSI에
의해 제공하고 있다.

타입                설명
char                8비트 부호를 갖는 ANSI 문자
wchar_t             16비트 unsigned short으로 typedef된 Unicode 문자
CHAR                char의 Win32 버전
WCHAR               wchar_t의 Wi32 버전
OLECHAR             wchar_t의 OLE버전
_TCHAR              char나 wchar_t로 변환될 일반적인 문자
LPSTR, LPCSTR       Win32 문자 포인터. C 버전의 const와 동일
LPWSTR, LPCWSTR     Win32 wide 문자 포인터
LPOLESTR, LPCOLESTR OLE의 wide 문자 포인터
LPTSTR, LPCTSTR     Win32 일반적인 문자 포인터
_T(str), _TEXT(str) 일반적인 문자열을 만드는 매크로
OLESTR(str)         일만적인 문자열을 만드는 OLE 매크로

일번적인 C++프로그래밍에서 당신은 Unicode나 ANSI빌드를 선택적으로 하기 위해 일번적인
버전의 함수들을 사용해야 할 것이다. 이와 비슷하게 String 클래스로 일반적인 것들을
내부로 숨겨서 쓸 수 있다.

알아두기  VC++ 4.0이전 버전은 OLE2ANSI라는 OLE Unicode문자열을 ANSI문자열로 자동으로
변환해주는 DLL을 갖고 있었다. 이 좋을듯한 DLL은 OLE프로그래밍을 이전보다 쉽게 해주었다.
이는 괜찮았지만, 성능면에서는 안좋았다. OLE2ANSI는 현재 OLE파일안에 선택적으로 쓸수있게
여전히 존재한다. WCHAR타입인 OLECHAR이 OLE 원형타입으로 쓰여졌으므로 이는 이 DLL안의
CHAR타입으로 변환되어 했었다. OLE2ANSI 심볼을 OLE 문자열을 ANSI 문자열로 자동으로
바꾸기 위해 쓰는 것은 하지 마라. 산타크로스는 없다. ( 너무 바보같이 느려지니까 그냥
자신이 바꾸면서 써라)

What is a BSTR?

전형적으로 typedef와 define을 많이 갖는 Windows Incldue파일의 스타일에 맞춰 BSTR 타입은
typedef로 생긴 것이다.

typedef wchar_t * BSTR;

음. BSTR는 실제로 Unicode 문자들을에 대한 포인터다. 다음은 같아 보이는가?

typedef wchar_t * LPWSTR;
typedef char * LPSTR;

만일 BSTR이 단지 문자들의 포인터라면 null로 끝나는 문자열과 어떻게 다른 것일까?
내부적으로 차이점은 문자열의 앞과 끝에 존재한다. 문자열의 길이는 long형 값으로
가리키는 시작주소의 바로전에 포함하게 된다.null은 문자열기능의 일부가 아니며,
그래서 당신은 null을 문자열 안에 문자로써 추가할 수 있다.

이는 기술적인 차이점이며, 철학적인 차이점은 BSTR은 신성시되어야 하는 것이라는 점
이다. 당신은 우리가 곧 알게될 매우 엄격한 규칙을 따라 문자들을 수정해야 한다.
OLE는 BSTR 할당과 재할당 그리고 파괴에 대한 함수들을 제공한다.

규칙 1: 할당, 파괴, BSTR 측정은 OLE API( Sys 함수들)로 통해야만 한다.

규칙 2: 당신이 소유한 문자열의 모든 문자들은 모두 유효하다.
SysStringLen에 의해 보고된 마지막 문자가 당신이 소유한 마지막 문자가 된다. 이는
null문자가 아닐 수 있으며, 당신은 HSTR안에서 null로 끝나는 문자열을 만들기 위해서
null 문자를 추가하는 바보같은 함수를 만들어선 안된다.

규칙 3: 당신은 소유한 문자열의 포인터를 변경할 수 있다. 그러나 오직 룰을 지켜야한다.
SysReAllocString이나 SysReAllocStringLen을 이용해야한다.

규칙 4: 당신은 by value로 BSTR을 전달 할 수 없다.
당신이 할 수 있는 오직 하나의 일은 넘겨 받은 문자열을 복사하는 것이며, 함수 내부에서
받은 인자를 변경하면 안된다. 다음처럼 함수에서 넘겨줄땐 아래처럼 해야 한다.

void DLLAPI TakeThisStringAndCopyIt(BCSTR bsIn);

BCSTR는 아래와 같이 선언되어 내부에서 변경하지 못하도록 의미지을수 있다.
typedef const wchar_t * const BCSTR;

Object Decription Lanauge(ODL)의 문장도 위와 같다.

void WINAPI TakeThisStringAndCopyIt([in] BCSTR bsIn);

BCSTR은 단지 BSTR의 별칭이며 MKTYPLIB는 const를 인식하지 못한다. 대신 [in]을 이용하여
받은 문자열의 내부를 변경하지 못할 것을 의미하게 된다.

규칙 5: in/out 인자로 쓰기 위해 BSTR을 참조로 줄 수 있다.
문자열의 내부를 변경하거나 기존 포인터를 바꾸기 위해서(SysReAlloc을 이용) BSTR을 C++에서
다음처럼 참조로 전달 할 수 있다.

void DLLAPI TakeThisStringAndGiveMeAnother(BSTR * pbsInOut);

ODL에서는

void WINAPI TakeThisStringAndGiveMeAnother([in, out] BSTR * pbsInOut);

규칙 6: 당신은 반드시 out 인자전달을 위해선 BSTR을 참조로 전달해야한다.

C++에서는 규칙 5와 같지만 ODL에서는

void WINAPI TakeNothingAndGiveMeAString([out] BSTR * pbsOut);

로 명시해야한다.

함수 내부에서는 다음처럼 값을 변경할 수 있게 된다.
// Allocate an output string.
*pbsOut = SysAllocString(L"As you like it");

규칙 7:당신은 반드시 리턴할일이 있다면 BSTR을 생성해야한다.

BSTR DLLAPI TransformThisString(BCSTR bsIn);

ODL버전은

BSTR WINAPI TransformThisString([in] BSTR bsIn);

// Make a new copy.
BSTR bsRet = SysAllocString(bsIn);
// Transform copy (uppercase it).
_wcsupr(bsRet);
// Return copy.
return bsRet;

규칙 8: null포인터는 BSTR에게 공백 문자열과 같다.
BSTR에서 공백 문자열은 하나의 null 문자를 가리키며 왼쪽에는 long값으로 0을
포함하게된다.

만일 인자 자체가 NULL인 경우는 문제가 있는 Unexpected이므로 원하는 로직이실행되는 것을 막아야한다.

if (bsIn != NULL) {
    wcsncat(bsRet, bsIn, SysStringLen(bsRet));
}


NULL포인터를 주어야 하는 expected된 로직이라면 공백 문자열을 넘기는 것을 막아야 한다.
cch = SearchPath(wcslen(bsPath) ? bsPath : (BSTR)NULL, bsBuffer,
                 wcslen(bsExt) ? bsExt : (BSTR)NULL, cchMax, bsRet, pBase);


비주얼 베이직에선 null pointer( vbNullString 상수로 표현되는 )는 공백 문자열과 동등하다.
따라서 아래의 VB코드는 유효하다.
Debug.Print vbNullString = ""

+ Recent posts