ATL字符串转码的陷阱

在代码中经常使用UTF8Wide Char的转换,每次手工调用WideCharToMultiByte转换很是麻烦,于是参考ATLW2AA2W这样的宏,自己写了两个宏:UTF82WW2UTF8

我写的UTF82WW2UTF8ATLW2AA2W一样,都是使用_alloca在栈上开辟临时空间存放转换结果的。后来,由于在使用中出现了一个函数中转换的次数过多导致栈溢出的问题。于是想改进这个宏。

比较新的ATL库里还有W2A_EX,这个宏第二个参数是本次转换在栈上最大开辟空间的长度(其实看过代码就知道,它无视了你的这个长度,总是使用一个硬编码的_ATL_SAFE_ALLOCA_DEF_THRESHOLD这个宏指定的1024作为最大开辟空间长度)。

我想要的正是这个功能。但上次仿写W2UTF8已经写伤了(看了实现你就知道了)。翻ATL源代码,找到好东西了:W2A_CP_EX,这个宏与W2A_EX类似,使用时多一个参数,用于指定转换的代码页。由于用WideCharToMultiByte来转换UTF8串时,是通过指定一个叫CP_UTF8的特殊代码页来实现的,于是,我天真的认为,只要指定CP_UTF8来使用W2A_CP_EX,就不用自己去写W2UTF8_EX了。

说用就用。在测试代码中试了一下,果然可以转,没多想就用上了。结果没几天,程序出现错误了,用这个宏,很多串都会转换失败。查代码,发现在这个宏调用WideCharToMultiByte时失败,LastError0x0000007aThe data area passed to a system call is too small。看现象应该是开辟的空间不够大。

这怎么可能呢?!查代码!结果让我大跌眼镜:ATLW2A_CP_EX宏在转换前计算需要开辟的空间尺寸时,没有用WideCharToMultiByte本身来计算,而是(出于什么我不清楚的设计的目的)使用了lstrlen得到串中的字符个数后乘以2sizeof(WCHAR))当做新串空间长。明显的,如果串中包含中文,转换后是3个字节,而不是2个字节。于是,空间不够是当然的了。

我认为,ATL这么做,主要是从性能上考虑,lstrlen不需要进行实际的转换,只是计算串中的字符数,性能当然好过WideCharToMultiByte

但这样的话,就出现了抽象泄漏:其它API指定代码页时,可以使用CP_UTF8,但这里指定代码页时就不能使用,你得知道它的实现才能正确使用。

一个好的设计,应当是不会被用错的。调用两次WideCharToMultiByte,时间复杂度不会变化。我认为这里没必要为了性能牺牲抽象。

一段好的代码,应当是严谨的。遵守API的使用条件非常重要。WideCharToMultiByte要求的转换后空间大小,只有他知道(你要知道还用他转换什么劲呀),那就应当让他告诉你准备多大的空间。

事情的结果:最终,我自己又仿照W2A_CP_EX重新实现了W2UTF8_EX,不同的是:我使用了WideCharToMultiByte来计算所需的空间。

 

附:

ATL的实现:

#define A2W_CP_EX(lpa, nChars, cp) (/

    ((_lpa_ex = lpa) == NULL) ? NULL : (/

        _convert_ex = (lstrlenA(_lpa_ex)+1),/

        FAILED(::ATL::AtlMultiply(&_convert_ex, _convert_ex, static_cast<int>(sizeof(WCHAR)))) ? NULL : /

        ATLA2WHELPER(   /

            (LPWSTR)_ATL_SAFE_ALLOCA(_convert_ex, _ATL_SAFE_ALLOCA_DEF_THRESHOLD), /

            _lpa_ex, /

            _convert_ex / sizeof(WCHAR), /

            (cp))))

 

#define W2A_CP_EX(lpw, nChars, cp) (/

    ((_lpw_ex = lpw) == NULL) ? NULL : (/

        _convert_ex = (lstrlenW(_lpw_ex)+1),/

        FAILED(::ATL::AtlMultiply(&_convert_ex, _convert_ex, static_cast<int>(sizeof(WCHAR)))) ? NULL : /

        ATLW2AHELPER(   /

            (LPSTR)_ATL_SAFE_ALLOCA(_convert_ex, _ATL_SAFE_ALLOCA_DEF_THRESHOLD), /

            _lpw_ex, /

            _convert_ex, /

            (cp))))

 

我的UTF8Wide Char转换宏:

 

#define USES_CONVERSION_UTF8 /

    USES_CONVERSION_EX; /

    int _convert_rhyme = 0; /

    (_convert_rhyme); /

    UINT _acp_rhyme = CP_UTF8 ; /

    (_acp_rhyme); /

    LPCTSTR _lpt_rhyme = NULL; /

    (_lpt_rhyme); /

    LPCSTR _lputf8_rhyme = NULL; /

    (_lputf8_rhyme)

 

 

#define UTF82W_EX(lputf8, nChars) (/

    ((_lputf8_rhyme = lputf8) == NULL) ? NULL : (/

    _convert_rhyme = MultiByteToWideChar(CP_UTF8, 0, _lputf8_rhyme, (int)(strlen(_lputf8_rhyme)), NULL, 0),/

    (_convert_rhyme == 0) ? NULL : /

    (RhymeUTF82WideCharHelper( /

    (LPWSTR)_ATL_SAFE_ALLOCA((_convert_rhyme + 1) * sizeof(WCHAR), nChars), /

    _lputf8_rhyme, /

    _convert_rhyme + 1))))/

 

 

#define UTF82W(lputf8) UTF82W_EX(lputf8, _ATL_SAFE_ALLOCA_DEF_THRESHOLD)

 

 

#define W2UTF8_EX(lpwide, nChars) (/

    ((_lpt_rhyme = lpwide) == NULL) ? NULL : (/

    _convert_rhyme = WideCharToMultiByte(CP_UTF8, 0, _lpt_rhyme, (int)(wcslen(_lpt_rhyme)), NULL, 0, NULL, NULL),/

    (_convert_rhyme == 0) ? NULL : /

    (RhymeWideChar2UTF8Helper( /

    (LPSTR)_ATL_SAFE_ALLOCA((_convert_rhyme + 1) * sizeof(char), nChars), /

    _lpt_rhyme, /

    _convert_rhyme + 1))))/

 

#define W2UTF8(lpwide) W2UTF8_EX(lpwide, _ATL_SAFE_ALLOCA_DEF_THRESHOLD)

 

 

    LPWSTR RhymeUTF82WideCharHelper(LPWSTR lpszWideString, LPCSTR lpszUTF8String, size_t nWideCharCount)

    {

        _ASSERTE(lpszWideString != NULL);

        _ASSERTE(lpszUTF8String != NULL);

        _ASSERTE(nWideCharCount <= TypeLimit<int>::Max());

        _ASSERTE(strlen(lpszUTF8String) <= TypeLimit<int>::Max());

 

        size_t nUTF8Length = strlen(lpszUTF8String);

 

        if(lpszWideString == NULL || lpszUTF8String == NULL ||

            nWideCharCount > TypeLimit<int>::Max() ||

            nUTF8Length > TypeLimit<int>::Max())

        {

            return NULL;

        }

 

        int nConverted = MultiByteToWideChar(

            CP_UTF8,

            0,

            lpszUTF8String,

            (int)(nUTF8Length),

            lpszWideString,

            (int)(nWideCharCount));

 

        if(nConverted == 0 && ERROR_NO_UNICODE_TRANSLATION == GetLastError())

        {

            return NULL;

        }

        lpszWideString[nConverted] = L'/0';

        return lpszWideString;

    }

 

    LPSTR RhymeWideChar2UTF8Helper(LPSTR lpszUTF8String, LPCWSTR lpszWideString, size_t nUTF8ByteCount)

    {

        _ASSERTE(lpszWideString != NULL);

        _ASSERTE(lpszUTF8String != NULL);

        _ASSERTE(nUTF8ByteCount <= TypeLimit<int>::Max());

        _ASSERTE(wcslen(lpszWideString) <= TypeLimit<int>::Max());

 

        size_t nWideCharCount = wcslen(lpszWideString);

 

        if(lpszWideString == NULL || lpszUTF8String == NULL ||

            nUTF8ByteCount > TypeLimit<int>::Max() ||

            nWideCharCount > TypeLimit<int>::Max())

        {

            return NULL;

        }

 

        int nConverted = WideCharToMultiByte(

            CP_UTF8,

            0,

            lpszWideString,

            (int)(nWideCharCount),

            lpszUTF8String,

            (int)(nUTF8ByteCount),

            NULL,

            NULL);

 

        if(nConverted == 0 && ERROR_NO_UNICODE_TRANSLATION == GetLastError())

        {

            return NULL;

        }

 

        lpszUTF8String[nConverted] = '/0';

        return lpszUTF8String;

    }

 

你可能感兴趣的:(String,api,测试,null,System,translation)