65.9K
CodeProject 正在变化。 阅读更多。
Home

TCHAR、WCHAR、LPSTR、LPWSTR、LPCTSTR(等等)是什么?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (255投票s)

2012 年 3 月 10 日

CPOL

12分钟阅读

viewsIcon

1212764

许多 Windows C++ 程序员对 TCHAR 和 LPCTSTR 等奇特的 TCHAR 数据类型感到困惑。在这里,我将简要地尝试驱散迷雾。

许多 C++ Windows 程序员对 TCHARLPCTSTR 等奇特的标识符感到困惑。在本文中,我将尽最大努力来驱散迷雾。

一般来说,一个字符可以由 1 个字节或 2 个字节表示。我们称 1 字节字符为 ANSI 字符——所有英文字符都通过这种编码表示。我们称 2 字节字符为 Unicode,它可以表示世界上所有的语言。

Visual C++ 编译器支持 charwchar_t 作为 ANSI 和 Unicode 字符的原生数据类型。尽管 Unicode 有更具体的定义,但为了便于理解,可以将其视为 Windows 操作系统用于多语言支持的双字节字符。

Unicode 比 Windows 使用的 2 字节字符表示法还有更多内容。Microsoft Windows 使用 UTF-16 字符编码。

如果您希望您的 C/C++ 代码独立于所使用的字符编码/模式,该怎么办?

建议:使用通用的数据类型和名称来表示字符和字符串。

例如,而不是替换

char cResponse; // 'Y' or 'N'
char sUsername[64];
// str* functions

wchar_t cResponse; // 'Y' or 'N'
wchar_t sUsername[64];
// wcs* functions

为了在您的语言中支持多语言(即 Unicode),您可以简单地以更通用的方式进行编码。

#include<TCHAR.H> // Implicit or explicit include
TCHAR cResponse; // 'Y' or 'N'
TCHAR sUsername[64];
// _tcs* functions

常规页中的以下项目设置描述了要用于编译的字符集:(常规 -> 字符集

这样,当您的项目被编译为 Unicode 时,TCHAR 将被翻译为 wchar_t。如果它被编译为 ANSI/MBCS,它将被翻译为 char。您可以自由使用 charwchar_t,项目设置不会影响这些关键字的任何直接使用。

TCHAR 定义如下:

#ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif

当您将字符集设置为“使用 Unicode 字符集”时,会定义宏 _UNICODE,因此 TCHAR 将表示 wchar_t。当字符集设置为“使用多字节字符集”时,TCHAR 将表示 char

同样,为了使用单一代码库支持多种字符集,并可能支持多语言,请使用特定的函数(宏)。与其使用 strcpystrlenstrcat(包括以 _s 结尾的安全版本);或者 wcscpywcslenwcscat(包括安全版本),您最好使用 _tcscpy_tcslen_tcscat 函数。

正如您所知,strlen 的原型是:

size_t strlen(const char*);

wcslen 的原型是:

size_t wcslen(const wchar_t* );

您最好使用 _tcslen,它的逻辑原型是:

size_t _tcslen(const TCHAR* );

WC 代表宽字符。因此,wcs 实际上是宽字符字符串。这样,_tcs 就代表 _T 字符字符串。您知道 _T 可以是 charwhat_t,从逻辑上讲。

但实际上,_tcslen(以及其他 _tcs 函数)**不是**函数,而是**宏**。它们的定义非常简单:

#ifdef _UNICODE
#define _tcslen wcslen 
#else
#define _tcslen strlen
#endif

您应该查阅 TCHAR.H 来查找更多此类宏定义。

您可能会问,为什么它们被定义为宏而不是函数?原因很简单:一个库或 DLL 可以导出具有相同名称和原型的单个函数(忽略 C++ 的重载概念)。例如,当您导出函数时:

void _TPrintChar(char);

客户端应该如何调用它?

void _TPrintChar(wchar_t);

_TPrintChar 无法神奇地转换为接受双字节字符的函数。必须有两个单独的函数:

void PrintCharA(char); // A = ANSI 
void PrintCharW(wchar_t); // W = Wide character

并且如下定义的简单宏可以隐藏差异:

#ifdef _UNICODE
void _TPrintChar(wchar_t); 
#else 
void _TPrintChar(char);
#endif

客户端只需调用:

TCHAR cChar;
_TPrintChar(cChar);

请注意,TCHAR_TPrintChar 都会映射到 Unicode 或 ANSI,因此 cChar 和函数参数将是 charwchar_t

宏避免了这些复杂性,并允许我们使用 ANSI 或 Unicode 函数来处理字符和字符串。大多数接受字符串或字符的 Windows 函数都是这样实现的,并且为了方便程序员,只有一个函数(一个宏!)就足够了。SetWindowText 是一个例子:

// WinUser.H
#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif // !UNICODE

很少有函数没有宏,只有以 WA 结尾的版本。一个例子是 ReadDirectoryChangesW,它没有 ANSI 等效项。


你们都知道我们使用双引号来表示字符串。以这种方式表示的字符串是 ANSI 字符串,每个字符为 1 字节。例如:

"This is ANSI String. Each letter takes 1 byte."

上面给出的字符串文本**不是** Unicode,并且对多语言支持是有限的。要表示 Unicode 字符串,您需要使用前缀 L。例如:

L"This is Unicode string. Each letter would take 2 bytes, including spaces."

注意字符串开头的 L,这使其成为 Unicode 字符串。所有字符(我再说一遍,**所有**字符)将占用两个字节,包括所有英文字母、空格、数字和空字符。因此,Unicode 字符串的长度始终是 2 字节的倍数。例如,长度为 7 个字符的 Unicode 字符串需要 14 个字节,以此类推。长度为 15 个字节的 Unicode 字符串,例如,在任何上下文中都无效。

一般来说,字符串将是 sizeof(TCHAR) 字节的倍数!

当您需要表示硬编码字符串时,可以使用:

"ANSI String"; // ANSI
L"Unicode String"; // Unicode

_T("Either string, depending on compilation"); // ANSI or Unicode
// or use TEXT macro, if you need more readability

没有前缀的字符串是 ANSI 字符串,带有 L 前缀的字符串是 Unicode 字符串,而 _TTEXT 指定的字符串将根据编译选项而定。同样,_TTEXT 只是宏,定义如下:

// SIMPLIFIED
#ifdef _UNICODE 
 #define _T(c) L##c
 #define TEXT(c) L##c
#else 
 #define _T(c) c
 #define TEXT(c) c
#endif

## 符号是标记粘贴运算符,它会将 _T("Unicode") 转换为 L"Unicode",其中传递的字符串是宏的参数——如果定义了 _UNICODE。如果未定义 _UNICODE_T("Unicode") 将仅表示 "Unicode"。标记粘贴运算符在 C 语言中就已存在,并且与 VC++ 或字符编码无关。

请注意,这些宏可用于字符串和字符。_T('R') 将转换为 L'R' 或简单的 'R'——前者是 Unicode 字符,后者是 ANSI 字符。

**不**,您不能使用这些宏将变量(字符串或字符)转换为 Unicode/非 Unicode 文本。以下无效:

char c = 'C';
char str[16] = "CodeProject";

_T(c);
_T(str);

粗体行将在 ANSI(多字节)构建中成功编译,因为 _T(x) 仅等于 x,因此 _T(c)_T(str) 分别等于 cstr。但是,当您使用 Unicode 字符集进行构建时,它将无法编译:

error C2065: 'Lc' : undeclared identifier
error C2065: 'Lstr' : undeclared identifier

我不想冒犯您的智商,去描述为什么以及那些错误是什么。

存在一套转换例程,用于将 MBCS 转换为 Unicode,反之亦然,我将很快解释。

需要注意的是,几乎所有接受字符串(或字符)的函数,尤其是在 Windows API 中,在 MSDN 等地方都会有通用的原型。例如,SetWindowTextA/W 函数可以归类为:

BOOL SetWindowText(HWND, const TCHAR*);

但是,正如您所知,SetWindowText 只是一个宏,根据您的构建设置,它将表示以下任一项:

BOOL SetWindowTextA(HWND, const char*);
BOOL SetWindowTextW(HWND, const wchar_t*);

因此,如果以下调用无法获取此函数的地址,请不要感到困惑!

HMODULE hDLLHandle;
FARPROC pFuncPtr;

hDLLHandle = LoadLibrary(L"user32.dll");

pFuncPtr = GetProcAddress(hDLLHandle, "SetWindowText");
//pFuncPtr will be null, since there doesn't exist any function with name SetWindowText !

User32.DLL 导出的函数是 SetWindowTextASetWindowTextW,而不是带有通用名称的函数。

有趣的是,.NET Framework 足够智能,可以从带有通用名称的 DLL 中定位函数:

[DllImport("user32.dll")]
extern public static int SetWindowText(IntPtr hWnd, string lpString);

没什么高深莫测的,只是一堆围绕 GetProcAddress if else

所有具有 ANSI 和 Unicode 版本的函数,其实际实现仅在 Unicode 版本中。这意味着,当您从代码中调用 SetWindowTextA,并传递一个 ANSI 字符串时——它会将 ANSI 字符串转换为 Unicode 文本,然后调用 SetWindowTextW。实际工作(设置窗口文本/标题/标题栏)将仅由 Unicode 版本执行!

再举一个例子,使用 GetWindowText 检索窗口文本。您调用 GetWindowTextA,并将 ANSI 缓冲区作为目标缓冲区传递。GetWindowTextA 将首先调用 GetWindowTextW,可能为其分配一个 Unicode 字符串(wchar_t 数组)。然后它会为您将该 Unicode 内容转换为 ANSI 字符串。

这种 ANSI 到 Unicode 的转换以及反向转换不仅限于 GUI 函数,还包括 Windows API 的整个集合,这些 API 接受字符串并有两种变体。一些例子是:

  • CreateProcess
  • GetUserName
  • OpenDesktop
  • DeleteFile
  • etc

因此,强烈建议直接调用 Unicode 版本。反过来,这意味着您**始终**应该以 Unicode 构建为目标,而不是 ANSI 构建——仅仅因为您多年来习惯使用 ANSI 字符串。是的,您可以保存和检索 ANSI 字符串,例如在文件中,或在您的 Messenger 应用程序中作为聊天消息发送。存在转换例程来满足此类需求。

注意:还存在另一个 typedef:WCHAR,它等同于 wchar_t


TCHAR 宏用于单个字符。您绝对可以声明一个 TCHAR 数组。如果您想表达一个字符指针常量字符指针,该怎么办?以下哪种是?

// ANSI characters 
foo_ansi(char*); 
foo_ansi(const char*); 
/*const*/ char* pString; 

// Unicode/wide-string 
foo_uni(WCHAR*); 
wchar_t* foo_uni(const WCHAR*); 
/*const*/ WCHAR* pString; 

// Independent 
foo_char(TCHAR*); 
foo_char(const TCHAR*); 
/*const*/ TCHAR* pString;

在阅读了 TCHAR 相关内容后,您**绝对会**选择最后一个作为您的选择。有更好的替代方法来表示字符串。为此,您只需包含 Windows.h。**注意**:如果您的项目隐式或显式地包含 Windows.h,则无需包含 TCHAR.H

首先,回顾旧的字符串函数以更好地理解。您知道 strlen

size_t strlen(const char*);

这可以表示为:

size_t strlen(LPCSTR);

其中符号 LPCSTRtypedef 为:

// Simplified
typedef const char* LPCSTR;  

其含义是:

  • LP - Long Pointer(长指针)
  • C - Constant(常量)
  • STR - String(字符串)

本质上,LPCSTR 表示(长)指向常量字符串的指针

让我们使用新式类型名称来表示 strcpy

LPSTR strcpy(LPSTR szTarget, LPCSTR szSource);

szTarget 的类型是 LPSTR,类型名称中没有 C。它定义为:

typedef char* LPSTR;

注意,szSourceLPCSTR,因为 strcpy 函数不会修改源缓冲区,因此具有 const 属性。返回类型是非常量字符串:LPSTR

好的,这些 str 函数用于 ANSI 字符串操作。但我们需要用于 2 字节 Unicode 字符串的例程。为此,提供了等效的宽字符 str 函数。例如,要计算宽字符(Unicode 字符串)的长度,您将使用 wcslen

size_t nLength;
nLength = wcslen(L"Unicode");

wcslen 的原型是:

size_t wcslen(const wchar_t* szString); // Or WCHAR*

这可以表示为:

size_t wcslen(LPCWSTR szString);

其中符号 LPCWSTR 定义为:

typedef const WCHAR* LPCWSTR;
// const wchar_t*

这可以分解为:

  • LP - Pointer(指针)
  • C - Constant(常量)
  • WSTR - Wide character String(宽字符字符串)

类似地,strcpy 的等效函数是 wcscpy,用于 Unicode 字符串:

wchar_t* wcscpy(wchar_t* szTarget, const wchar_t* szSource)

这可以表示为:

LPWSTR wcscpy(LPWSTR szTarget, LPWCSTR szSource);

其中目标是非常量宽字符串(LPWSTR),源是常量宽字符串。

存在一套等效的 wcs 函数对应于 str 函数。str 函数将用于纯 ANSI 字符串,而 wcs 函数将用于 Unicode 字符串。

虽然我之前建议使用 Unicode 原生函数,而不是仅 ANSI 或 TCHAR合成的函数。原因很简单——您的应用程序应该仅为 Unicode,并且您**不**应该关心 ANSI 构建的代码可移植性。但为了完整起见,我提到了这些通用映射。

要计算字符串长度,您可以使用 _tcslen 函数(宏)。通常,它的原型是:

size_t _tcslen(const TCHAR* szString); 

或者,表示为:

size_t _tcslen(LPCTSTR szString);

其中类型名称 LPCTSTR 可分类为:

  • LP - Pointer(指针)
  • C - Constant(常量)
  • T = TCHAR
  • STR = String(字符串)

根据项目设置,LPCTSTR 将映射到 LPCSTR(ANSI)或 LPCWSTR(Unicode)。

注意strlenwcslen_tcslen 将返回字符串的**字符**数,而不是字节数。

通用的字符串复制例程 _tcscpy 定义为:

size_t _tcscpy(TCHAR* pTarget, const TCHAR* pSource);

或者,更通用的形式是:

size_t _tcscpy(LPTSTR pTarget, LPCTSTR pSource);

您可以推断出 LPTSTR 的含义!

使用示例

首先,一段损坏的代码:

int main()
{
    TCHAR name[] = "Saturn";
    int nLen; // Or size_t

    lLen = strlen(name);
}

在 ANSI 构建中,此代码将成功编译,因为 TCHAR 将是 char,因此 name 将是一个 char 数组。调用 strlen 来处理 name 变量也将顺利进行。

好的。让我们使用 UNICODE/_UNICODE 定义(即项目设置中的“使用 Unicode 字符集”)来编译相同的代码。现在,编译器将报告一系列错误:

  • 错误 C2440:“初始化”:无法从“const char [7]”转换为“TCHAR []”
  • 错误 C2664:“strlen”:无法将参数 1 从“TCHAR []”转换为“const char *”

程序员会开始犯错,将其纠正为这样(第一个错误):

TCHAR name[] = (TCHAR*)"Saturn";

这并不能让编译器满意,因为无法从 TCHAR* 转换为 TCHAR[7]。当原生 ANSI 字符串传递给 Unicode 函数时,也会出现同样的错误。

nLen = wcslen("Saturn");
// ERROR: cannot convert parameter 1 from 'const char [7]' to 'const wchar_t *'

不幸的是(或者幸运地),这个错误可以通过简单的 C 风格类型转换来错误地纠正:

nLen = wcslen((const wchar_t*)"Saturn");

您会认为您在指针方面又获得了一个经验等级!您错了——代码将产生不正确的结果,在大多数情况下只会导致访问冲突。这样进行类型转换就像在需要一个 80 字节的结构时传递一个 float 变量(从逻辑上讲)。

字符串 "Saturn" 是 7 个字节的序列:

'S' (83) 'a' (97) 't'(116) 'u' (117) 'r'(114) 'n'(110) '\0' (0)

但是,当您将相同的一组字节传递给 wcslen 时,它会将每 2 个字节视为一个单独的字符。因此,前两个字节 [97, 83] 将被视为一个值为:24915(97<<8 | 83)的字符。这是一个 Unicode 字符:?。下一个字符由 [117, 116] 等表示。

当然,您并没有传递那些中文字符,但错误的类型转换却做到了!因此,了解类型转换**不起作用**非常重要!所以,对于第一行初始化,您必须这样做:

TCHAR name[] = _T("Saturn");

这会根据编译结果转换为 7 个字节或 14 个字节。对 wcslen 的调用应该是:

wcslen(L"Saturn");

在上面提供的示例程序代码中,我使用了 strlen,它在 Unicode 构建时会产生错误。无效的解决方案是 C 风格的类型转换:

lLen = strlen ((const char*)name);

在 Unicode 构建中,name 将是 14 个字节(7 个 Unicode 字符,包括空字符)。由于字符串 "Saturn" 只包含英文字母,它们可以使用原始 ASCII 表示,因此 Unicode 字母 'S' 将表示为 [83, 0]。其他 ASCII 字符将表示为一个零在它们旁边。请注意,'S' 现在表示为**2 字节**值 83。字符串的结束将由值为 0 的**两个字节**表示。

因此,当您将这样的字符串传递给 strlen 时,第一个字符(即第一个字节)将是正确的(对于 "Saturn" 是 'S')。但是第二个字符/字节将指示字符串的结束。因此,strlen 将返回不正确的值 1 作为字符串的长度。

正如您所知,Unicode 字符串可能包含非英文字符,strlen 的结果将更加不确定。

总之,类型转换是行不通的。您需要将字符串表示为正确的形式,或者使用 ANSI 到 Unicode(反之亦然)的转换例程。

(还有更多内容待添加,敬请关注!)


现在,我希望您能理解以下签名:

BOOL SetCurrentDirectory( LPCTSTR lpPathName );
DWORD GetCurrentDirectory(DWORD nBufferLength,LPTSTR lpBuffer);

继续。您一定见过一些函数/方法要求您传递**字符数**,或者返回字符数。嗯,就像 GetCurrentDirectory 一样,您需要传递字符数,而不是字节数。例如:

TCHAR sCurrentDir[255];
 
// Pass 255 and not 255*2 
GetCurrentDirectory(sCurrentDir, 255);

另一方面,如果您需要分配字符数,则必须分配正确的字节数。在 C++ 中,您可以使用 new

LPTSTR pBuffer; // TCHAR* 

pBuffer = new TCHAR[128]; // Allocates 128 or 256 BYTES, depending on compilation.

但是,如果您使用 mallocLocalAllocGlobalAlloc 等内存分配函数;您必须指定字节数!

pBuffer = (TCHAR*) malloc (128 * sizeof(TCHAR) );

正如您所知,需要对返回值进行类型转换。malloc 参数中的表达式确保它分配了所需的字节数——并为所需的字符数腾出了空间。

© . All rights reserved.