在代码中经常使用UTF8与Wide Char的转换,每次手工调用WideCharToMultiByte转换很是麻烦,于是参考ATL的W2A、A2W这样的宏,自己写了两个宏:UTF82W、W2UTF8。
我写的UTF82W、W2UTF8与ATL的W2A、A2W一样,都是使用_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时失败,LastError为0x0000007a:The data area passed to a system call is too small。看现象应该是开辟的空间不够大。
这怎么可能呢?!查代码!结果让我大跌眼镜:ATL的W2A_CP_EX宏在转换前计算需要开辟的空间尺寸时,没有用WideCharToMultiByte本身来计算,而是(出于什么我不清楚的设计的目的)使用了lstrlen得到串中的字符个数后乘以2(sizeof(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))))
我的UTF8与Wide 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;
}