VS下使用多字符集编码和Unicode字符集编码的总结

编写MFC程序的时候,总遇到字符集转换的问题,这里总结一下,方便大家使用。
在多字节字符集编码下,设置如下环境:
VS下使用多字符集编码和Unicode字符集编码的总结_第1张图片
这时CString与char数组是可以互相转换的,而如果改成“使用Unicode字符集”,设置如下:
VS下使用多字符集编码和Unicode字符集编码的总结_第2张图片
原来的代码就会报很多错误,诸如:
error C2664: “Cxxxxx::ConvertStringtoBtye”: 不能将参数 1 从“wchar_t *”转换为“char *”
这是因为使用Unicode编码,编译器已经把CString视为宽字符处理了,就不能再自动转为char *了。那么代码中用到的地方都得处理下,简单的如使用字符串常量,加上_T宏就可以解决问题,如:

AfxMessageBox("编码类型不正确!");

改为

AfxMessageBox(_T("编码类型不正确!"));
strTest.Replace(" ", "");

改为

strTest.Replace(_T(" "), _T(""));

而对于一些函数,MFC为了兼容老版本,定义了两种类型,一种以W结尾的宽字符函数,另一种以A结尾的多字符函数。例如SQLGetInstalledDrivers函数,如果是多字符集就调用SQLGetInstalledDriversA,如果是Unicode字符集,就调用SQLGetInstalledDriversW:
VS下使用多字符集编码和Unicode字符集编码的总结_第3张图片
对于这种函数,使用时我们也得进行区分,为了兼容性,也使用UNICODE分别处理:

WORD cbBufMax = 2000;
WORD cbBufOut;

#ifdef  UNICODE
	char szBuf[2001];
	char *pszBuf = szBuf;

	// Get the names of the installed drivers ("odbcinst.h" has to be included )
	if(!SQLGetInstalledDrivers(szBuf, cbBufMax, & cbBufOut))
	{
		m_sExcelDriver = "";
	}
#else
	wchar_t szBuf[2001];
	wchar_t *pszBuf = szBuf;

	// Get the names of the installed drivers ("odbcinst.h" has to be included )
	if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
	{
		m_sExcelDriver = _T("");
	}
#endif

对于一些操作字符和字符串的库函数,也是有区别的:

#ifdef  UNICODE
	// Search for the driver...
	do
	{
		if (wcsstr(pszBuf, _T("Excel")) != 0)
		{
			// Found !
			m_sExcelDriver = CString(pszBuf);
			break;
		}
		pszBuf = wcschr(pszBuf, '\0') + 1;
	} while (pszBuf[1] != '\0');
#else
	// Search for the driver...
	do
	{
		if( strstr( pszBuf, "Excel" ) != 0 )
		{
		// Found !
			m_sExcelDriver = CString( pszBuf );
			break;
		}
		pszBuf = strchr( pszBuf, '\0' ) + 1;
	}
	while( pszBuf[1] != '\0' );

为了有更好的兼容性,应该选择两个版本通用的函数,比如字符串转长整形,最好使用_tcstol函数来代替使用跟字符集相关的strtol或wcstol函数,类似的还有_ttoi、_ttof之类的转换函数。

在Unicode字符集下写文件的时候,对于长度操作要注意,一个宽字符是要写两个长度的:

	CFile fileSave;
	CString strGetData(_T("写入测试"));
	CString strPath(_T("test.txt")); 

	if (!fileSave.Open(strPath, CFile::modeCreate |CFile::modeNoTruncate | CFile::modeWrite))
	{
		return;
	}

	wchar_t wch = 0xFEFF;
	fileSave.Write(&wch, sizeof(wchar_t));

	fileSave.Write(strGetData.LockBuffer(), wcslen(strGetData)*2);
	strGetData.UnlockBuffer();
	fileSave.Close();

使用Unicode字符集还有一大问题,就是CString与char之间的相互转换,以下函数就我总结的转换函数,char转CString函数:

CString Char2CString(char *pChar)
{
	int charLen = strlen(pChar); // 计算pChar所指向的字符串大小
	int len = MultiByteToWideChar(CP_ACP, 0, pChar, charLen, NULL, 0); // 计算多字节字符的大小
	wchar_t *pWChar = new wchar_t[len + 1]; // 为宽字节字符数申请空间 
	MultiByteToWideChar(CP_ACP, 0, pChar, charLen, pWChar, len); // 多字节编码转换成宽字节编码
	pWChar[len] = '\0';  

	// 将wchar_t数组转换为CString
	CString str;
	str.Append(pWChar);
	delete[] pWChar;

	return str;
}

CString转char*函数:

char* CString2Char(CString str)
{
	DWORD dwCount = str.GetLength();   
	int len = WideCharToMultiByte(CP_ACP, 0, str, dwCount, NULL, 0, NULL, NULL); // 获取宽字节字符的大小
	char *pChar = new char[len + 1];

	WideCharToMultiByte(CP_ACP, 0, str, dwCount, (char *)pChar, len, NULL, NULL); // 宽字节编码转换成多字节编码 
	pChar[len] = '\0'; // 注意以'\0'结束
	return pChar;
}

从代码可以看出,这里CString转char*需要new一个内存空间,使用后得记得delete掉才行。如果不习惯释放空间,那CString转char时最好不要开辟空间,万一忘记delete就造成内存泄露了,可以写一个改进版的转换函数:

static int CStrintToCharWithoutNew(CString str, char *buf)
{
	int len = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
	if (len > 0)
		WideCharToMultiByte(CP_ACP, 0, str, -1, buf, len, NULL, NULL);
	buf [len] = '\0'; // 注意以'\0'结束
	return len;
}

这样使用后就不需要再记得delete了,前提是数组得定义的足够大:

char recvData[200];
CString testdata = _T("测试转换");
len = CStrintToCharWithoutNew(testdata, recvData);

上面的方法虽然基础,但显得麻烦了些,char*转CString只需要使用A2T()或A2W()宏即可:

char cBuf[] = "hello 世界";
CString str = _T("");

USES_CONVERSION;
str = A2T(cBuf);
// str = A2W(cBuf);

如果项目没包含头文件#include 需要自己加上,USES_CONVERSION宏一定要放在使用前,否则会报错:

error C2065: “_lpa”: 未声明的标识符

类似的CString转char只需使用T2A或W2A宏即可。对于网上说的char转CString使用Format方法,如下:

char cBuf[] = "hello 世界";
CString str = _T("");
str.Format(_T("%s"), cBuf);

经测试,在Unicode编码下是不行的,cBuf数组就不是宽字符,改为下面写法就可以了:

str.Format(_T("%s"), _T("hello 世界"));

有时函数的参数是指针类型LPTSTR(多字符下是char ,Unicode下实际是wchar_t),那么如何把CString转为LPTSTR呢,下面两种方法都可以:

CString str("hello 世界"); 
LPTSTR lp = (LPTSTR)(LPCTSTR)str;
// LPTSTR lp = str.GetBuffer(); 

上面的代码在多字符集和Unicode字符集下都可以使用的,不过使用GetBuffer()时,使用完记得调用ReleaseBuffer()释放内存。这里有个细节,细心的读者可能会发现,CString创建的时候没有加_T宏:

CString str(_T("hello 世界"));

这里CString的构造函数自动为我们处理了,所以不用担心编码问题的。说到这里,您一定对_T宏感兴趣了,这个宏到底做了什么呢?在tchar.h文件中可以看到对它的定义,摘录下来如下:

#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

#define __T(x)      L ## x           // 编码为 Unicode
#define __T(x)      x                // 编码为 多字符

其实它根据不同的编码环境来转换字符的,在Unicode下,会把字符前面加个L代表宽字符,所以下面的定义只能在Unicode下使用:

wchar_t wBuf[] = _T("hello 世界");

就等同于:

wchar_t wBuf[] = L"hello 世界";

L为宽字符的定义宏,调试时可以发现宽字符变量的值是带L的。而在多字符集下面就报错了,因为转义成了:

wchar_t wBuf[] = "hello 世界;

有时候会见到WCHAR和TCHAR宏,不用慌,其实他们也是wchar_t的变体,类似很多宏定义都可以在WinNT.h文件中找到。说到TCHAR,有必要说明一下,是MFC为了统一字符集操作而定义的类型,它跟_T宏类似,在不同的字符集下有不同的定义:

typedef wchar_t TCHAR;   // 编码为 Unicode
typedef char TCHAR;      // 编码为 多字符

那么对于刚才的问题就有了解决方案,定义数组时如下定义:

TCHAR tBuf[] = _T("hello 世界");

这样就不会因为编译时选择不同的字符集而造成编译出错了。这下好了,按照这些标准写的代码,无论怎么选字符集都不会报错,本以为万事大吉了,可是世事难料啊,项目要调用两个别人写的库,一个是Unicode字符集的,而另一个是多字符集编译的,怎么选都是报错啊。
比如这里选通用的Unicode字符集,分析一下多字符集库报错的原因,头文件的函数使用了LPCTSTR类型参数,而这个类型也是随不同的字符集而代表不同类型的,在WinNT.h文件有定义:

typedef LPCWSTR PCTSTR, LPCTSTR;
typedef __nullterminated CONST WCHAR *LPCWSTR, *PCWSTR;

分析可见在Unicode下实际上是WCHAR 类型,而那个库原本在多字符集条件下编译时应该转为LPCSTR(实际上是char)类型的:

typedef __nullterminated CONST CHAR *LPCSTR, *PCSTR;

所以对于对外接口来说,写成通用字符集的参数是很不好的,人家编译时选择的字符集不一定和你一样,很容易造成连接错误的,直接写成char*类型才是正道啊。

你可能感兴趣的:(基础知识)