2012. 6. 1. 17:02

ATL의 A2W, W2A 버그를 수정한 손쉬운 String Encoding 함수 공유(smart pointer)

ATL의 A2W, W2A, A2W_CP, W2A_CP의 코드 내부를 보면,
WideCharToMultiByte(...)나 MultiByteToWideChar(...)를 우선 호출하여 버퍼의 크기를 구해서 다시
호출하는 것이 아니라, 들어온 Source 문자열의 길이를 2배로 한다던지 하여 "대충" 구현되어 있습니다.

UTF8같은 경우는 2배 이상인 경우가 있어, A2W_CP, W2A_CP와 같은 함수 사용시 오류(NULL 리턴)가 발생하더군요.

그리고, A2W, W2A 함수군들은 명시적인 Free를 해주지 않아도 되기 때문에 굉장히 선호됩니다.
다만, 그 이유가 stack으로 부터 메모리를 할당 받기 때문입니다.
그래서, stack 크기 만큼 제약을 받게 됩니다. (대략 1~2MB 정도)

만일, 10MB 정도 크기의 문자열을 A2W등으로 변환한다면 여전히 실패하게 됩니다.
(흔하지 않겠지만...)

즉, ATL에서 제공하는 A2W, W2A 함수군의 단점(혹은 버그)은 다음과 같습니다.

  • WideCharToMultiByte(...)나 MultiByteToWideChar(...)를 통해 길이를 동적으로 구하는 것이 아니라, 대충(들어온 문자열 길이의 2배) 문자열 길이를 계산하여 할당 ==> UTF8 같은 경우 오류 발생함
  • 10MB 같이 큰 문자열을 사용할 수 없음

과 같습니다.

사용법은,

{
    // 변수선언
    ...
    USES_SMARTCONVERSION;

    LPCWSTR lpszW = NULL;
    LPCSTR  lpszA = NULL;

    lpszA = SMART_W2A_CP(TEXT("hello, world, 안녕"), CP_UTF8);
    lpszW = SMART_A2W_CP(lpszA, CP_UTF8);
    lpszW = SMART_A2W   ("good", CP_UTF8);
    ...
    // lpszA, lpszW에 대해 Free 혹은 delete [] 를 명시적으로 할 필요가 없다
    ...
}

와 같습니다. 즉, ATL의 사용법과 동일하며, 앞에 SMART_를 붙이면 됩니다. 물론 내부에서 ATL의 SmartPointer를 사용하므로, ATL을 사용하셔야 합니다. CAutoPtr classCAutoPtrArray class를 직접 가져오신다면 ATL 없이도 사용가능할 겁니다.

아래 코드에서는 _CrtSetReportMode(...) ... { BLOCK } _CrtDumpMemoryLeaks()과 같이 되어 있는데,
BLOCK에서 메모리 leak이 발생하면 output에 report를 해주게 됩니다. 아래 코드를 실행하면 report가 없는데, 이는 아래 부분의 BLOCK 영역은 leak이 없다는 뜻입니다. 그러니 맘편히 사용하셔도 됩니다.
view plain을 통해 코드를 확인하십시요.

#include "stdafx.h"
#include 
#include 

namespace SMARTConversion
{
	// 사용법 (W2A, A2W 함수군과 유사함)
	//
	// {
	//		INT		nMyLocalVariable	= 0;
	//		LPCWSTR	lpszConv			= NULL;
	//		...
	//		USES_SMARTCONVERSION;
	//
	//		...
	//
	//		// lpszConv는 USES_smartCONVERSION이 등록된 Scope에서 살아있는 변수가 된다.
	//		// lpszConv는 NULL 리턴하지 않는다. 오류시 ""가 전달된다.
	//		lpszConv = SMART_A2W   ("hello, world, 안녕하세요");
	//		lpszConv = SMART_A2W_CP("hello, world, 안녕하세요", CP_UTF8);
	//		...

	LPCWSTR _smart_a2w(IN LPCSTR lpszSourceA, IN UINT CodePage, OUT CAutoPtrArray& cAutoPtrArrayW)
	{
		LPWSTR			lpszRtnValueW	= NULL;
		INT				nCchLength		= 0;
		CAutoPtr	cAutoPtr;

		if (NULL == lpszSourceA)
		{
 			assert(FALSE);
			return TEXT("");
		}

		// 길이를 구하자.
		nCchLength = ::MultiByteToWideChar(CodePage, 0, lpszSourceA, -1, NULL, 0);
		if (0 == nCchLength)
		{
			return TEXT("");
		}

		lpszRtnValueW = new WCHAR[nCchLength+1];
		if (NULL == lpszRtnValueW)
		{
			assert(FALSE);
			return TEXT("");
		}
		ZeroMemory(lpszRtnValueW, sizeof(WCHAR)*(nCchLength+1));

		if (0 == ::MultiByteToWideChar(CodePage, 
									   0, 
									   lpszSourceA, 
									   -1, 
									   lpszRtnValueW, 
									   nCchLength))
		{
			// MBCS 변환 실패
			assert(FALSE);
			delete [] lpszRtnValueW;
			lpszRtnValueW = NULL;
			return TEXT("");
		}

		// 동적 할당한 것을 smart pointer에 등록
		cAutoPtr.Attach(lpszRtnValueW);

		try
		{
			// caller가 관리하는 smart pointer pool에 등록하여, lpszRtnValue의 life-time을 같게한다.
			cAutoPtrArrayW.Add(cAutoPtr);
		}
		catch(...)
		{
			// 예외적인 상황이므로, 에러 리턴
			// 만일 Add가 수행되지 못했다면, lpszRtnValue는 cAutoPtr.Attach(...)에
			// 의해 본 함수가 종료되면 Free된다.
			// 그래서 caller 보호를 위해 ""를 전달한다.
			return TEXT("");
		}

		return lpszRtnValueW;
	}

	LPCSTR _smart_w2a(IN LPCWSTR lpszSourceW, IN UINT CodePage, OUT CAutoPtrArray& cAutoPtrArrayA)
	{
		LPSTR			lpszRtnValueA	= NULL;
		INT				nCchLength		= 0;
		CAutoPtr	cAutoPtr;

		if (NULL == lpszSourceW)
		{
			assert(FALSE);
			return "";
		}

		// 길이를 구하자.
		nCchLength = ::WideCharToMultiByte(CodePage, 0, lpszSourceW, -1, NULL, 0, NULL, NULL);
		if (0 == nCchLength)
		{
			return "";
		}

		lpszRtnValueA = new CHAR[nCchLength+1];
		if (NULL == lpszRtnValueA)
		{
			assert(FALSE);
			return "";
		}
		ZeroMemory(lpszRtnValueA, sizeof(CHAR)*(nCchLength+1));

		if (0 == ::WideCharToMultiByte(CodePage, 
									   0, 
									   lpszSourceW, 
									   -1, 
									   lpszRtnValueA, 
									   nCchLength, 
									   NULL, 
									   NULL))
		{
			// MBCS 변환 실패
			assert(FALSE);
			delete [] lpszRtnValueA;
			lpszRtnValueA = NULL;
			return "";
		}

		// 동적 할당한 것을 smart pointer에 등록
		cAutoPtr.Attach(lpszRtnValueA);

		try
		{
			// caller가 관리하는 smart pointer pool에 등록하여, lpszRtnValue의 life-time을 같게한다.
			cAutoPtrArrayA.Add(cAutoPtr);
		}
		catch(...)
		{
			// 예외적인 상황이므로, 에러 리턴
			// 만일 Add가 수행되지 못했다면, lpszRtnValue는 cAutoPtr.Attach(...)에
			// 의해 본 함수가 종료되면 Free된다.
			// 그래서 caller 보호를 위해 ""를 전달한다.
			return "";
		}

		return lpszRtnValueA;
	}

	#define USES_SMARTCONVERSION CAutoPtrArray _cAutoPtrArrayA; CAutoPtrArray _cAutoPtrArrayW;

	#define	SMART_A2W_CP(lpszSourceA, CodePage) (SMARTConversion::_smart_a2w(lpszSourceA, CodePage, _cAutoPtrArrayW));
	#define SMART_A2W   (lpszSourceA)           (SMARTConversion::_smart_a2w(lpszSourceA, CP_ACP,   _cAutoPtrArrayW));
	#define	SMART_W2A_CP(lpszSourceW, CodePage) (SMARTConversion::_smart_w2a(lpszSourceW, CodePage, _cAutoPtrArrayA));
	#define SMART_W2A   (lpszSourceW)           (SMARTConversion::_smart_w2a(lpszSourceW, CP_ACP,   _cAutoPtrArrayA));
}


int _tmain(int argc, _TCHAR* argv[])
{
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);   
	_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);   
	_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); 
	{
		USES_SMARTCONVERSION;
		LPCWSTR lpszW = NULL;
		LPCSTR  lpszA = NULL;


		lpszA = SMART_W2A_CP(TEXT("hello, world, 안녕"), CP_UTF8);
		lpszW = SMART_A2W_CP(lpszA, CP_UTF8);
	}

	_CrtDumpMemoryLeaks();
	return 0;
}
10MB 제한때문에, stack을 사용하지 않고 ATL의 smart pointer를 사용하고 있습니다.
즉, USES_SMARTCONVERSION을 선언한 Block 내부가 벗어나면 자동으로 Memory 해제됩니다.
내부에서 여러번 SMART_x2x가 호출될 수 있기 때문에, smart pointer는 CAutoPtrArray를 이용하여 관리하게 됩니다.