详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换

2023-05-16

目录

  • 1、概述

  • 2、Visual Studio中的字符编码

  • 3、ANSI窄字节编码

  • 4、Unicode宽字节编码

  • 5、UTF8编码

  • 6、如何使用字符编码

  • 7、三种字符编码之间的相互转换(附源码)

  • 7.1、ANSI编码与Unicode编码之间的转换

  • 7.2、UTF8编码与Unicode编码之间的转换

  • 7.3、ANSI编码与UTF8编码之间的转换

  • 8、Windows系统对使用ANSI窄字节字符编码的程序的兼容

  • 9、字符编码导致程序启动失败的案例

1、概述

在日常的软件开发过程中,会时不时地去处理不同编码格式的字符串,特别是在处理文件路径的相关场景中,比如我们要通过路径去读写文件、通过路径去加载库文件等。常见的字符编码格式有ANSI窄字节编码、Unicode宽字节编码以及UTF8可变长编码。在Linux系统中,主要使用UTF8编码;在Windows系统中,既支持ANSI编码,也支持Unicode编码。

通用的大小写字母和数字则使用全球统一的固定编码,即ASCII码。

ANSI编码是各个国家不同语种下的字符编码,其字符的编码值只在该语种中有效,不是全球统一编码的,比如中文的GB2312编码就是简体中文的ANSI编码。

Unicode编码则是全球统一的双字节编码,所有语种的字符在一起统一的编码,每个字符的编码都是全球唯一的。

UTF8编码是一种可变长的宽字节编码,也是一种全球统一的字符编码。

本文将以WIndows中使用Visual Studio进行C++编程时需要处理的字符编码问题为切入点,详细讲解一下字符编码的相关内容。

2、Visual Studio中的字符编码

在Visual Studio中编写C++代码时,该如何指定字符串的编码呢?其实很简单,使用双引号括住的字符串,使用的就是ANSI窄字节编码;使用L+双引号括住的字符串,使用的就是Unicode宽字节编码,如下所示:

char* pStr = "This is a Test.";    // ANSI编码
WCHAR* pWStr = L"This is a Test."; // Unicode宽字节编码

我们也可以使用_T宏定义来指定字符串的编码格式:


TCHAR* pStr = _T("This is a Test.");

设置_T后,则由工程配置属性中的字符集设置来确定到底是使用哪种编码:

如果选择多字节字符集,_T就被解释为双引号,即使用ANSI窄字节编码;如果选择Unicode字符集,_T就被解释为L,即使用Unicode宽字节编码。

其实,如果在工程配置中选择使用Unicode字符集,工程中会添加一个_UNICODE宏,如下所示:

如果选择多字节字符集,则没有_UNICODE宏。代码中正是通过这个宏来判定到底使用哪种编码的,比如对_T的判断:

#ifdef  _UNICODE#define  _T(X)    L(X)#else#define  _T(X)    (X)#endif// _UNICODE

和字符编码相对应的,Windows系统提供两个版本的API,比如给窗口设置文字的API函数,一个是支持ANSI窄字节编码的SetWindowTextA(ANSI窄字节版本),一个是支持Unicode宽字节编码的SetWindowTextW(Wide宽字节版本)。我们也可以直接调用SetWindowText,然后由_UNICODE宏判断到底使用哪个版本,如下:

#ifdef _UNICODE#define SetWindowText  SetWindowTextW#else#define SetWindowText  SetWindowTextA#endif// !UNICODE

3、ANSI窄字节编码

ANSI编码是不同语种下的字符编码,比如GB2312字符编码就是简体中文的本地编码。

ANSI编码是个本地范畴,只适用于对应的语种,每个字符的编码不是全球唯一的编码,只在对应的语种中有效。对于中文GB2312编码的字符串,如果当成英文的ANSI编码来解析,则结果会是乱码!

但是对于大小写英文字母和数字的ANSI编码,是字符ASCII码,英文字母和数字的ACSII码是全球统一的,比如大写字母A的ASCII码是65(十六进制是41H),数字0的ASCII码是48(十六进制是30H)。所以在所有语种中,大小写字母及数字的ANSI编码,都是能识别的。不同语种下的本地文字字符,一般是不能相互识别的。

使用中文ANSI编码的字符串开发的程序(代码中使用的都是中文字符串,使用的是ANSI窄字节编码),拿到俄文操作系统中可能显示的都是乱码,因为在俄文的ANSI编码中只识别俄文的ANSI编码出来的字符串,无法识别中文ANSI编码的字符串。这里主要有两类字符乱码问题,一是UI界面上显示的文字是乱码;二是使用路径去创建文件或访问文件时会因为路径中的字符是乱码,导致文件创建或访问失败。

4、Unicode宽字节编码

Unicode编码是全球统一的字符编码,每个语种下的每个字符的编码值都是全球唯一的,即在Unicode编码集中可以识别每个语种下的所有字符。所以为了实现软件对多语种(多国语言)的支持,我们在开发软件时要选择Unicode字符编码,使用Unicode编码的字符串,调用Unicode版本的API。

系统在提供包含字符串参数的API时,都会提供两个版本,一个是ANSI版本的,一个是Unicode版本的,主要体现在对字符串编码的处理上,比如SetWindowTextA(ANSI版本)和SetWindowTextW(Wide宽字节Unicode版本)。我们可以直接调用W版本API,但一般我们调用API时,我们不指定调用哪个版本,是通过设置工程属性中的编码格式来确定使用哪个版本:

#ifdef _UNICODE#define SetWindowText  SetWindowTextW#else#define SetWindowText  SetWindowTextA#endif// !UNICODE

具体情况已在上面的“Visual Studio中的字符编码”章节中详细讲述,此处不再赘述。

在Unicode编码中,每个字符都占两个字节。对于大小写字母和数字,当他们出现在字符串中时,对应的内存中存放的是它们的ASCII码值,只占一个字节,在Unicode 2字节编码中,高位将填充0。

5、UTF8编码

UTF8编码是可变长字符编码格式,是一种紧凑型存储的编码格式,也是一种宽字节的、全球统一的编码格式。UTF8编码中的所有字符,包括不同语种下面的字符,都是全球唯一编码的,在所有的系统都能识别出来。

UTF8编码中,能用一个字节存放的,就用一个字节存放,比如大小写字母和数字,在字符串中存放的ASCII码,只需要一个字节去存放就够了。所以在UTF8编码中,大小写字母和数字只占一个字节。我们常用的中文字符,一个字符则占用3个字节。

UTF8编码之所以称之为可变长的,是因为其根据字符需要的实际存储空间大小来编码的,比如大小写字母和数字的存储只需要1个字节就够了,所以它们只占一个字节,而一个中文字符则占三个字节。

6、如何使用字符编码

Windows系统主要使用Unicode编码,Linux则使用UTF8编码,后台服务器一般使用的都是Linux系统,而客户端是运行在Windows操作系统上的。一般客户端与服务器交互的数据的字符串编码统一使用全球统一编码的UTF8编码。

客户端收到UTF8编码的字符串后,需要将UTF8字符换转换后显示在界面上。如果客户端使用的是Unicode编码字符集,将UTF8编码的字符串转换成Unicode编码的字符串后再显示到界面上;如果客户端使用的是多字节ANSI编码,则需要再将Unicode编码的字符串转成ANSI编码的字符串。

这里注意一下,UTF8编码的字符串要转成ANSI编码的,不能直接将UTF8转成ANSI,需要先将UTF8转成Unicode,然后再将Unicode转成ANSI。

为了实现软件对多语种(多国语言)的支持,我们在开发Windows软件时要选择Unicode字符编码,使用Unicode编码的字符串,调用Unicode版本的API。

此外,对于一些开源的项目,提供的API接口中有字符串参数的,一般都明确指定字符串编码为UTF8。因为一般情况下开源库都支持跨平台,既支持Windows平台,也支持Linux平台,所以要选择使用通用的、大家都是识别的UTF8编码。

比如在轻便型数据库sqlite开源库中,用于打开数据库文件的接口sqlite3_open,就明确指定使用UTF8编码的字符串:


**
** ^URI hexadecimal escape sequences(%HH) are supported within the path and
** query components of a URI. A hexadecimal escape sequence consists of a
** percent sign - "%" - followed by exactly two hexadecimal digits 
** specifying an octet value. ^Before the path or query components of a
** URI filename are interpreted, they are encoded using UTF-8 and all 
** hexadecimal escape sequences replaced by a single byte containing the
** corresponding octet. If this process generates an invalid UTF-8 encoding,
** the results are undefined.
**
** <b>Note to Windows users:</b>  The encoding used for the filename argument
** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever
** codepage is currently defined.  Filenames containing international
** characters must be converted to UTF-8 prior to passing them into
** sqlite3_open() or sqlite3_open_v2().
**
** <b>Note to Windows Runtime users:</b>  The temporary directory must be set
** prior to calling sqlite3_open() or sqlite3_open_v2().  Otherwise, various
** features that require the use of temporary files may fail.
**
** See also: [sqlite3_temp_directory]
*/
SQLITE_API intsqlite3_open(
  constchar *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API intsqlite3_open16(
  constvoid *filename,   /* Database filename (UTF-16) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API intsqlite3_open_v2(
  constchar *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb,         /* OUT: SQLite db handle */int flags,              /* Flags */constchar *zVfs        /* Name of VFS module to use */
);

对于使用Unicode编码的Windows程序,代码中使用的都是Unicode编码的字符串,在调用sqlite3_open接口之前,需要将Unicode编码的字符串转成UTF8编码的。如果收到开源库中回调上来的UTF8编码的字符串数据,则需要将UTF8编码的字符串转成Unicode后,才能显示到UI界面上,才能使用转码后的Unicode字符串去调用Windows系统API。

7、三种字符编码之间的相互转换(附源码)

有朋友曾经提出这样的疑问,是不是我在Windows下把一个双引号括起来的ANSI窄字节字符串赋值给WCHAR宽字节的指针:


WCHAR* pStr = "测试字符串";

字符串就能自动转换成Unicode宽字节?答案是否定的,这样的赋值操作并不会做字符编码转换,右侧的仅仅是字符串的首地址,作为地址,可以赋值给很多数据类型,比如int、void*、char*等等。

那可能有人会说,那为啥我在Unicode下,将一个ANSI编码的字符串传给MFC库中的CString类对象时会自动转换成Unicode宽字符呢?这和上面的情况不一样的,是因为CString类重载了赋值操作符函数,在函数内部做了字符编码的转换,代码如下:

const CUIString& CUIString::operator=(LPCSTR lpsz)
{
	int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
	AllocBeforeWrite(nSrcLen);
	_ANSIToUnicode(m_pchData, lpsz, nSrcLen+1);
	ReleaseBuffer();
	return *this;
}

一般情况下,是需要我们自己去编写字符编码转换的代码的。下面来看一下,我们在进行Windows C++编程时,需要调用哪些API接口实现上述三种编码之间的转换。

7.1、ANSI编码与Unicode编码之间的转换

ANSI转成Unicode的代码如下:

/*=============================================================================
函 数 名: AnsiToUnicode
功    能: 实现将char型buffer(ANSI编码)中的内容安全地拷贝到指定的WChar型(Unicode编码)的buffer中
参    数: char*  pchSrc [in]          源字符串
          WCAHR* pchDest [out]        目标buf    
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidAnsiToUnicode( constchar* pchSrc, WCHAR* pchDest, int nDestLen ){
	if ( pchSrc == NULL || pchDest == NULL )
	{
		return;
	}
 
	int nTmpLen = MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, NULL, 0);
	WCHAR* pWTemp = new WCHAR[nTmpLen + 1];
	memset(pWTemp, 0, (nTmpLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, pWTemp, nTmpLen + 1);
 
	UINT nLen = wcslen(pWTemp);
	if (nLen + 1 > (nDestLen / sizeof(WCHAR)))
	{
		wcsncpy(pchDest, pWTemp, nDestLen / sizeof(WCHAR) - 1);
		pchDest[nDestLen / sizeof(WCHAR) - 1] = 0;
	}
	else
	{
		wcscpy(pchDest, pWTemp);
	}
 
	delete []pWTemp;
}

Unicode转成ANSI的代码如下:

/*=============================================================================
函 数 名: UnicodeToAnsi
功    能: 实现将WCHAR型buffer(Unicode编码)中的内容安全地拷贝到指定的char型(ANSI编码)的buffer中
参    数: WCHAR*  pchSrc [in]         源字符串
		  char*   pchDest[out]        目标buf
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidUnicodeToAnsi(const WCHAR* pchSrc, char* pchDest, int nDestLen ){
	if ( pchDest == NULL || pchSrc == NULL )
	{
		return;
	}
 
	const WCHAR* pWStrSRc = pchSrc;
	int nTmplen = WideCharToMultiByte(CP_ACP, 0, pWStrSRc, -1, NULL, 0, NULL, NULL);
	char* pTemp = newchar[nTmplen + 1];
	memset(pTemp, 0, nTmplen + 1);
	WideCharToMultiByte(CP_ACP, 0, pWStrSRc, -1, pTemp, nTmplen + 1, NULL, NULL);
 
	int nLen = strlen(pTemp);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pTemp, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pTemp;
}

7.2、UTF8编码与Unicode编码之间的转换

UTF8转成Unicode的代码如下:

/*=============================================================================
函 数 名: Utf8ToUnicode
功    能: 实现将char型的buffer(utf8编码)中的内容安全地拷贝到指定的WCHAR型buffer(Unicode编码)中
参    数: char* pchSrc [in]          源字符串
          WCHAR* pchDest [out]     目标buf		 
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidUtf8ToUnicode( constchar* pchSrc, WCHAR* pchDest, int nDestLen ){
	if ( pchSrc == NULL || pchDest == NULL )
	{
		return;
	}
 
	int nTmpLen = MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, NULL, 0);
	WCHAR* pWTemp = new WCHAR[nTmpLen + 1];
	memset(pWTemp, 0, (nTmpLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, pWTemp, nTmpLen + 1);
 
	UINT nLen = wcslen(pWTemp);
	if (nLen + 1 > (nDestLen / sizeof(WCHAR)))
	{
		wcsncpy(pchDest, pWTemp, nDestLen / sizeof(WCHAR) - 1);
		pchDest[nDestLen / sizeof(WCHAR) - 1] = 0;
	}
	else
	{
		wcscpy(pchDest, pWTemp);
	}
 
	delete []pWTemp;
}

Unicode转成UTF8的代码如下:

/*=============================================================================
函 数 名: UnicodeToUtf8
功    能: 实现将WCHAR型buffer(Unicode编码)中的内容安全地拷贝到指定的char型的buffer(utf8编码)中
参    数: WCAHR* pchSrc [in]          源字符串
          char* pchDest [out]     目标buf	  
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidUnicodeToUtf8(const WCHAR* pchSrc, char* pchDest, int nDestLen );
{
	if ( pchDest == NULL || pchSrc == NULL )
	{
		return;
	}
 
	const WCHAR* pWStrSRc = pchSrc;
	int nTmplen = WideCharToMultiByte(CP_UTF8, 0, pWStrSRc, -1, NULL, 0, NULL, NULL);
	char* pTemp = newchar[nTmplen + 1];
	memset(pTemp, 0, nTmplen + 1);
	WideCharToMultiByte(CP_UTF8, 0, pWStrSRc, -1, pTemp, nTmplen + 1, NULL, NULL);
 
	int nLen = strlen(pTemp);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pTemp, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pTemp;
}

7.3、ANSI编码与UTF8编码之间的转换

ANSI与UTF8之间是不能直接转换的,需要先转成Unicode之后才能转到目标编码。

ANSI转成UTF8的代码如下:

/*=============================================================================
函 数 名: AnsiToUtf8
功    能: 实现将char型buffer(ANSI编码)中的内容安全地拷贝到指定的char型的buffer(utf8编码)中
参    数: char* pchSrc [in]          源字符串
          char* pchDest [out]     目标buf
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidAnsiToUtf8( constchar* pchSrc, char* pchDest, int nDestLen ){
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}
 
	// 先将ANSI转成Unicode
	int nUnicodeBufLen = MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, NULL, 0);
	WCHAR* pUnicodeTmpBuf = new WCHAR[nUnicodeBufLen + 1];
	memset(pUnicodeTmpBuf, 0, (nUnicodeBufLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_ACP, 0, pchSrc, -1, pUnicodeTmpBuf, nUnicodeBufLen + 1);
 
	// 再将Unicode转成utf8
	int nUtf8BufLen = WideCharToMultiByte(CP_UTF8, 0, pUnicodeTmpBuf, -1, NULL, 0, NULL, NULL);
	char* pUtf8TmpBuf = newchar[nUtf8BufLen + 1];
	memset(pUtf8TmpBuf, 0, nUtf8BufLen + 1);
	WideCharToMultiByte(CP_UTF8, 0, pUnicodeTmpBuf, -1, pUtf8TmpBuf, nUtf8BufLen + 1, NULL, NULL);
 
	int nLen = strlen(pUtf8TmpBuf);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pUtf8TmpBuf, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pUtf8TmpBuf);
	}
 
	delete[]pUtf8TmpBuf;
	delete[]pUnicodeTmpBuf;
}

UTF8转成ANSI的代码如下:

/*=============================================================================
函 数 名: Utf8ToAnsi
功    能: 实现将char型buffer(utf8编码)中的内容安全地拷贝到指定的char型的buffer(ANSI编码)中
参    数: char* pchSrc [in]          源字符串
		  char* pchDest [out]     目标buf
		  int nDestLen [in]           目标buf长度(注意:以字节为单位,不是以字符个数为单位)
注    意: 无
返 回 值: 无
=============================================================================*/voidUtf8ToAnsi(constchar* pchSrc, char* pchDest, int nDestLen){
	if (pchSrc == NULL || pchDest == NULL)
	{
		return;
	}
 
	// 先将utf8转成Unicode
	int nUnicdeBufLen = MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, NULL, 0);
	WCHAR* pUnicodeTmpBuf = new WCHAR[nUnicdeBufLen + 1];
	memset(pUnicodeTmpBuf, 0, (nUnicdeBufLen + 1) * sizeof(WCHAR));
	MultiByteToWideChar(CP_UTF8, 0, pchSrc, -1, pUnicodeTmpBuf, nUnicdeBufLen + 1);
 
	// 再将Unicode转成utf8
	int nAnsiBuflen = WideCharToMultiByte(CP_ACP, 0, pUnicodeTmpBuf, -1, NULL, 0, NULL, NULL);
	char* pAnsiTmpBuf = newchar[nAnsiBuflen + 1];
	memset(pAnsiTmpBuf, 0, nAnsiBuflen + 1);
	WideCharToMultiByte(CP_ACP, 0, pUnicodeTmpBuf, -1, pAnsiTmpBuf, nAnsiBuflen + 1, NULL, NULL);
 
	int nLen = strlen(pAnsiTmpBuf);
	if (nLen + 1 > nDestLen)
	{
		strncpy(pchDest, pAnsiTmpBuf, nDestLen - 1);
		pchDest[nDestLen - 1] = 0;
	}
	else
	{
		strcpy(pchDest, pTemp);
	}
 
	delete []pAnsiTmpBuf;
	delete []pUnicodeTmpBuf;
}

8、Windows系统对使用ANSI窄字节字符编码的程序的兼容

现在的Windows程序基本都用Unicode字符编码了,工程属性中将字符集都设置成了Unicode字符集,代码中都使用Unicode编码的字符串。但是还有一些老的程序使用的还是ANSI窄字节的字符。那这些老的程序如何才能在外文的操作系统中正常运行呢?微软提供了一种兼容这些老程序的办法。

可以到Windows控制面板的区域语言设置中将非Unicode语言设置成程序中使用的字符语种即可,相关设置的操作步骤截图如下:

在上图中选择程序中字符使用的语种即可。

下面我们来看看使用ANSI编码的程序放到外文操作系统中运行为什么会出现乱码。假设将某程序中使用的是中文ANSI窄字节编码的字符串,放到英文操作系统中运行,默认情况下,UI界面上会显示乱码。至于为什么会显示乱码,是因为英文操作系统中默认情况下设置的非Unicode语言是英语(美国):

这个非Unicode语言设置直接影响我们调用MultiByteToWideChar和WideCharToMultiByte接口中的CP_ACP标记对应的本地ANSI字符集编码库。在上面界面中如果将非Unicode语言设置成英语(美国),则使用英文的ANSI字符编码库;如果设置成中文简体,则使用中文简体的ANSI字符集编码库。

程序中调用API函数SetWindowTextA给程序中的窗口设置文字或标题时,传入的字符串是ANSI窄字节编码的,而SetWindowTextA函数内部及底层的流程中会使用本地设置的ANSI字符集编码库将ANSI编码的字符串转成Unicode编码的字符串后再设置到窗口中,最终界面上看到的文字是Unicode编码的文字。所以在将中文字符转换成Unicode时,如果使用的是本地设置的英文字符集编码进行转换,则会出现乱码;如果使用中文简体的字符集编码进行转换,则能正常显示。

所以,要让使用中文ANSI编码字符的程序能在英文操作系统中正常显示并运行,需要将英文操作系统中区域语言设置项中的“非Unicode程序的语言”设置成中文才行。

9、字符编码导致程序启动失败的案例

几天前正好排查了一例因为字符编码导致的程序启动失败的实例,在这里简单的说一下。客户将软件安装到一个包含中文字符的路径中,点击启动软件没反应,软件始终启动不了,也没有弹出什么报错的提示框。客户于是向我们反馈了这个问题。

我们使用向日葵远程到客户的机器上,经对比发现,如果我们将软件安装到默认的C:\Program Files(X86)的英文路径下,程序是能正常启动的,所以我们初步怀疑可能是字符编码引起的问题。重新将软件安装到D盘包含中文字符的路径后,我们用windbg启动软件,刚启动windbg中就检测到看异常,异常发生在加载主程序依赖库的过程中。

启动软件的exe主程序时,会将该exe依赖的所有库依次加载到进程空间中,待所有的库都加载起来后,才会将exe主程序模块启动起来,才能看到软件的主界面。

如果在加载库时产生了异常,整个启动过程将被终止,软件也就无法启动了。

异常发生在加载音视频编解码库mediaproc.dll中,于是在windbg中输入kn命令,查看异常时的函数调用堆栈(事先已经取来了pdb符号文件)。调用堆栈显示时崩溃在mediaproc.dll库的DllMain函数中,加载dll库时都会调用到该接口。

根据调用堆栈中显示的代码行号,到编解码库的源代码中查看,发现是崩溃在一个函数接口指针的调用上,有可能是遇到空指针了。一般情况下,使用windbg实时调试时是能看到函数中的局部变量及类对象内存中的值,但这次有点特殊,看不到内存中的值。

于是和负责维护音视频编解码库的同事沟通了一下, 编解码库mediaproc.dll在DllMain中会使用绝对路径(当前exe主程序的路径)去调用LoadLibrary去动态加载更底层的库,然后调用GetProcAddress把底层库的接口都拿出来保存到指针变量中。编解码库mediaproc.dll是调用ANSI版本的API函数GetModuleFileNameA获取exe主程序的路径,问题就出在这个函数的调用上,这个函数获取的路径中包含乱码。

D盘包含中文字符的文件夹在系统中是能正常显示的,为啥获取的路径中会包含乱码呢?于是查看了客户Windows操作系统版本,是Windows10 IOT版本,经常见到旗舰版、专业版和教育版,这个IOT版本还是第一次遇到!于是又去查看控制面板区域语言中的非Unicode语言选项设置:

系统中设置的非Unicode语言为英语(美国),这样系统指向的本地ANSI字符编码库就是英语(美国)的ANSI字符编码库。

D盘中包含中文字符的文件夹在系统中能正常显示的,为啥调用GetModuleFileNameA获取到的路径中会有乱码呢?系统中显示的中文字符是Unicode编码的,当我们调用ANSI版本的GetModuleFileNameA获取路径时,GetModuleFileNameA函数内部会将Unicode编码的字符串转成ANSI编码的,转换时使用的是系统指向的本地ANSI字符编码库,也就是英语(美国)的ANSI字符编码库,而英语(美国)的ANSI字符编码库根本不识别中文字符,所以出现了乱码!

GetModuleFileNameA返回的路径中包含乱码,导致LoadLibrary失败,导致GetProcAddress返回NULL值,从而导致call这个NULL地址产生了异常!

对于当前出问题的编解码库,需要修改一下代码,需要调用Unicode版本的接口。目前临时的解决办法有两个:

1)将软件安装在英文路径中;

2)在控制面板的区域语言中将非Unicode语言改成简体中文。

我们的软件已经声称做到了对多语种的支持,虽然UI层已经支持Unicode了,但底层的库因为是不同开发团队开发维护的,需要再逐一排查一下了!

以上就是详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换的详细内容,更多关于C++ 字符编码的资料请关注编程学习网其它相关文章!

来源:详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换-织梦云编程网 (dedeyun.com)

一文带你弄懂C++中的ANSI、Unicode和UTF8三种字符编码及相互转换_c++ utf8_dvlinker的博客-CSDN博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换 的相关文章

  • 尝试通过 JDBC 将 UTF-8 插入 MySQL 时出现“错误的字符串值”?

    这就是我的连接设置方式 Connection conn DriverManager getConnection url dbName useUnicode true characterEncoding utf 8 userName pass
  • 如何查明我的字符串是否包含“micro”Unicode 字符?

    我有一个包含实验室数据的 Excel 电子表格 如下所示 g L ppb 我想测试希腊字母 是否存在 如果发现我需要做一些特别的事情 通常 我会写这样的东西 if cell StartsWith matchSequence lt unive
  • Python 删除额外的特殊 unicode 字符

    我正在 python 中处理一些文本 它内部已经采用 unicode 格式 但我想删除一些特殊字符并用更标准的版本替换它们 我目前有一条看起来像这样的线路 但它变得越来越复杂 我发现它最终会带来更多麻烦 tmp infile lower r
  • 如何在 Flutter 中解码 Gzip Http 响应?

    我是颤振新手 我正在发出网络请求 并且得到了正确的响应 但数据已被压缩 我已经在 Swift 中解压了相同的内容 但是对于 Flutter 我无法做到这一点 有人可以帮忙吗 这是我尝试过的 import dart convert impor
  • 使用 python3 查找表情符号的宽度

    我尝试使用 python 中的模式打印字母 A def printA length height symbol a for i in range length for i in range height for i in range hei
  • android中如何将字符串转换为unicode

    我正在解析一些unicodes from json to my android应用程序 API 给出unicodes像这样的图标 ue600 当我将这个unicode直接添加到textview like textview setText u
  • 如何为所有语言创建字母数字正则表达式?

    我今天遇到了这个问题 此正则表达式仅匹配英语 a zA Z0 9 如果我需要支持这个世界上的任何语言 我应该编写什么正则表达式 如果您使用字符类简写和 Unicode 识别正则表达式引擎 您就可以做到这一点 这 wclass 匹配 单词字符
  • 如何使用 vim 更改文件的编码?

    我习惯使用 vim 修改文件的行结尾 file file file ASCII text with CRLF line terminators vim file set ff mac wq file file file ASCII text
  • PHP UTF-8 问题 - 如果我在 PHP 中创建一个字符串...它是 UTF-8 格式吗?

    在 PHP 中 如果我创建一个像这样的字符串 str bla bla here is my string 然后我可以使用 mbstring 函数将该字符串作为 UTF8 进行操作吗 Will this work str mb strlen
  • 国际化和非 US-ASCII、Latin1 或 Win1252 的密码

    当用户输入最能以 Unicode 或其他非拉丁字符编码表示的内容时 您如何处理服务密码 具体来说 可以使用西里尔字母密码作为Oracle的密码吗 如果密码以 UTF 8 形式提供 您如何根据 Windows 身份验证机制验证用户的密码 我对
  • 如何在 JavaScript 中从代理对构造 UTF-16 字符?

    以下计算 Unicode 代码点的 UTF 16 代理对 戴着医用口罩的脸 https emojipedia org face with medical mask 但是如何从代理对构造字符以在字符串中使用呢 const codepoint
  • Python unicode 字符代码?

    有没有办法将 Unicode 字符 插入 Python 3 中的字符串 例如 gt gt gt import unicode gt gt gt string This is a full block s unicode charcode U
  • 在网络浏览器上显示 UTF-16 字符

    我打印了一些 UTF 16 编码的字符并尝试在 Firefox 中显示它 它显示为 所以我进入 工具 gt 编码 并将编码从 UTF 8 更改为 UTF 16 我也尝试直接在 HTML 中更改字符集 但是 当我这样做时 我的页面完全被符号淹
  • 我应该在密码中支持 Unicode 吗?

    我想允许我的用户使用 Unicode 作为密码 不过我发现很多网站不支持这一点 例如 Gmail Hotmail 所以我想知道是否有一些我忽略的技术或可用性问题 我在想 如果有什么问题的话 那一定是可用性问题 因为默认情况下 NET 接受
  • 电子邮件正则表达式将如何处理新的 unicode 域?

    Since 2009年10月 互联网 名称指定公司和 Numbers ICANN 批准了创建 国家 地区代码顶级域名 ccTLD 在互联网上使用 母语 IDNA 标准 脚本 我很确定大多数网站当前使用的标准正则表达式不会将它们标记为有效 还
  • \d 只匹配0-9位数字?

    据我所知 d应该匹配非英文数字 例如 但它在 JavaScript 中不能正常工作 看这个jsFiddle http jsfiddle net xZpam http jsfiddle net xZpam 这是正常行为吗 JavaScript
  • JTextPane 的等宽字体/符号

    我想使用 JTextPane 构建类似控制台的输出 因此我使用等宽字体 textpane setFont new Font Font MONOSPACED Font PLAIN 12 这适用于所有类型的字母 如 a z 0 9 等 字符 但
  • XElement 和 UTF-8 问题

    我有一个 NET Web 服务 asmx 而不是 svc 它通过 HTTP POST 接受字符串 它接受的字符串是 xml 信息集 然后我通过 XElement Parse 进行解析 解析为 XElement 实例后 我将一个节点添加到该实
  • ☺ 不在移动版本中呈现

    我如何获得特殊角色 笑脸在移动浏览器中正确呈现 li a href http goo gl GjxlI title target blank span style font size 20px span a li 它在大多数浏览器上显示为
  • 如何将UTF-8编码的汉字从MySql正确导出到SQL

    过去三天我们正在与严重的问题作斗争 我们从PhpmyAdmin导出MySql数据库文件 数据库条目中写入的数据是带有UTF 8字符集的中文 导出后将其转换为拉丁字符集 现在我们正在将此数据库SQl文件导入到其他主机 我们在UTF 8和排序规

随机推荐

  • Libnet简单学习

    简单了解后 xff0c 建议直接查看源码 xff0c 以获得其他函数 xff1a libnet libnet functions h File Reference 本文仅列举个别常用函数 libnet工作流程 xff08 1 xff09 通
  • Java模拟生产者消费者模型【仅代码 + 注释】

    模拟仓库容量为1 xff0c 1个消费者 xff0c 1个生产者的生产者消费者模型 span class token keyword package span span class token namespace com span clas
  • PHP8.2 Apache24 Windows10安装步骤

    PHP8 2 Apache24 Windows10安装步骤 1 官网地址 https httpd apache org download cgi 修改1 xff1a Define SRVROOT D WorkSoft Apache Apac
  • navicat乱码和激活

    乱码 xff1a 1 将export LANG 61 34 zh CN UTF 8 34 2 工具 gt 选项下常规 编辑器 xff0c 记录下的字体选Noto sans CJK相近字体 激活 xff1a 第一次执行start navica
  • OnclickListener

    相信很多像我一样的新手学习ANDROID开发会遇到这个问题 xff0c 通过这几天的归类和总结 xff0c 将我的理解写在下面 xff0c 欢迎大家一起前来讨论 xff1a 以按钮BUTTON的监听事件为例 xff0c 以下的监听实现都是等
  • 关于Activity的onNewIntent方法

    前言 onNewIntent方法想必大家都知道 xff0c 是和Activity的启动模式结合起来使用的 xff0c 可以这个方法具体什么情况下被调用 xff0c 如何使用你清楚了吗 xff1f 今天就来一探究竟 xff0c 扫清疑惑 实验
  • 【算法学习】二分查找 binary-search (Java 参考)

    题目描述 给定一个 n 个元素有序的 xff08 升序 xff09 整型数组 nums 和一个目标值 target xff0c 写一个函数搜索 nums 中的 target xff0c 如果目标值存在返回下标 xff0c 否则返回 1 思路
  • 【算法学习】二维数组检索 search-a-2d-matrix(Java)

    题目描述 请写出一个高效的在m n矩阵中判断目标值是否存在的算法 xff0c 矩阵具有如下特征 xff1a 每一行的数字都从左到右排序 每一行的第一个数字都比上一行最后一个数字大 例如 xff1a 对于下面的矩阵 xff1a 1 3 5 7
  • 【算法学习】二维数组查找(Java)

    一 题目描述 此题源于 剑指 offer 在一个二维数组中 xff08 每个一维数组的长度相同 xff09 xff0c 每一行都按照从左到右递增的顺序排序 xff0c 每一列都按照从上到下递增的顺序排序 请完成一个函数 xff0c 输入这样
  • 【算法学习】链表数相加(Java)

    一 题目表述 给定两个代表非负数的链表 xff0c 数字在链表中是反向存储的 xff08 链表头结点处的数字是个位数 xff0c 第二个结点上的数字是十位数 xff09 xff0c 求这个两个数的和 xff0c 结果也用链表表示 输入 xf
  • 【算法学习】最长公共子序列(Java)

    一 题目描述 给定两个字符串 text1 和 text2 xff0c 返回这两个字符串的最长公共子序列的长度 一个字符串的 子序列 是指这样一个新的字符串 xff1a 它是由原字符串在不改变字符的相对顺序的情况下删除某些字符 xff08 也
  • JFugue4.0 中文说明

    简介 由音符 八度 音长 音色 xff08 乐器 xff0c 默认乐器为钢琴 xff09 组成 和弦 连音 速冻 控制器 键签名 Jfugue 可以简单并且允许工程师去快速创建音乐的原因是 MusicString xff0c 一个特殊格式描
  • 【算法学习】有效括号 valid-parentheses (Java 参考)

    题目描述 给定一个只包括 xff0c xff0c xff0c xff0c xff0c 的字符串 xff0c 判断字符串是否有效 有效字符串需满足 xff1a 左括号必须用相同类型的右括号闭合 左括号必须以正确的顺序闭合 注意空字符串可被认为
  • 【算法学习】约瑟夫环(含公式思想总结)(Java)

    目录 一 题目描述二 思路分析思路1 xff1a 模拟思路2 xff1a 数学方法递推公式 xff1a 推导思路why 为什么可以倒推how 如何倒推 三 参考代码四 测试连接 一 题目描述 这个问题是以弗拉维奥 约瑟夫命名的 xff0c
  • Ubuntu下matplotlib中文乱码

    原因 xff1a 由于matplotlib的默认安装字体不支持中文格式 解决思路 xff1a 将默认字体换成可以支持中文字体包 matplotlib默认的字体文件为 anaconda3 lib python3 7 site packages
  • 【算法学习】n个骰子的点数(Java)

    目录 一 题目描述二 思路分析核心公式公式推导依据 三 参考代码四 测试连接 一 题目描述 把n个骰子扔在地上 xff0c 所有骰子朝上一面的点数之和为s 输入n xff0c 打印出s的所有可能的值出现的概率 你需要用一个浮点数数组返回答案
  • 自动复制粘贴“机器人”(go)

    背景 最近遇到个问题 xff0c 需要将 html 批量转换为 markdown xff0c 尝试过很多转换库结果并不理想 xff0c 发现通过复制粘贴的方式效果十分不错 xff08 mac xff0c 从chrome浏览器 xff0c 复
  • Go Error 错误处理总结

    Go Error 一 设计理念 简单考虑成功而不是只有成功没有隐藏的控制流完全交给调用者控制 errorError are values xff08 Rob Pike xff09 二 错误与异常 go 中的错误处理主要使用到error 和
  • Linux创建新用户和key登陆

    1 root用户身份登陆 2 新增一个用户 useradd m new user name 3 切换至新用户 su new user name 4 生成公钥和私钥 ssh keygen t rsa 一路回车 5 cd ssh 6 cat i
  • 详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换

    目录 1 概述 2 Visual Studio中的字符编码 3 ANSI窄字节编码 4 Unicode宽字节编码 5 UTF8编码 6 如何使用字符编码 7 三种字符编码之间的相互转换 xff08 附源码 xff09 7 1 ANSI编码与