什么是TCHAR字符串和Win32 API函数的“A”或“W”版本?

什么是TCHAR字符串,例如LPTSTRLPCTSTR以及如何使用这些字符串? 当我在Visual Studio中创建一个新项目时,它为我创建了这个代码:

 #include  int _tmain(int argc, _TCHAR* argv[]) { return 0; } 

例如,我如何连接所有命令行参数?

如果我想打开第一个命令行参数给出的名称的文件,我该怎么做? Windows API定义了许多函数的“A”和“W”版本,例如CreateFileCreateFileACreateFileW ; 那么这些如何彼此不同以及我应该使用哪一个?

首先我要说的是,最好不要将TCHAR用于新的Windows项目,而应直接使用Unicode。 关于实际答案:

字符集

我们需要了解的第一件事是字符集如何在Visual Studio中工作。 项目属性页面有一个选项可以选择使用的字符集:

  • 没有设置
  • 使用Unicode字符集
  • 使用多字节字符集

项目属性页面 - 字符集

根据您选择的三个选项中的哪一个,更改了许多定义以适应所选字符集。 有三个主要类:字符串,来自tchar.h字符串例程和API函数:

  • ‘Not Set’对应于使用ANSI编码的TCHAR = char ,其中您使用系统的标准8位代码页来表示字符串。 所有tchar.h字符串例程都使用基本的char版本。 所有使用字符串的API函数都将使用API​​函数的“A”版本。
  • ‘Unicode’对应于使用UTF-16编码的TCHAR = wchar_t 。 所有tchar.h字符串例程都使用wchar_t版本。 所有使用字符串的API函数都将使用API​​函数的“W”版本。
  • ‘Multi-Byte’对应于TCHAR = char ,使用一些多字节编码方案。 所有tchar.h字符串例程都使用多字节字符集版本。 所有使用字符串的API函数都将使用API​​函数的“A”版本。

相关阅读: 关于visual studio 2010中的“字符集”选项

TCHAR.h标题

tchar.h头是一个帮助器,用于对字符串上的C字符串操作使用通用名称,切换到给定字符集的正确函数。 例如, _tcscat将切换到strcat (未设置), wcscat (unicode)或_mbscat (mbcs)。 _tcslen将切换到strlen (未设置), wcslen (unicode)或strlen (mbcs)。

通过将所有_txxx符号定义为评估为正确函数的宏来进行切换,具体取决于编译器开关。

它背后的想法是你可以使用编码不可知类型TCHAR (或_TCHAR )和与它们相关的编码不可知函数,来自tchar.h ,而不是string.h中的常规字符串函数。

同样, _tmain被定义为mainwmain 。 另请参阅: C ++中_tmain()和main()之间的区别是什么?

帮助宏_T(..)被定义为获取正确类型的字符串文字, "regular literals"L"wchar_t literals"

请参阅此处提到的警告: TCHAR是否仍然相关? – dan04的回答

_tmain示例

对于问题中main的示例,以下代码将作为命令行参数传递的所有字符串连接成一个。

 int _tmain(int argc, _TCHAR *argv[]) { TCHAR szCommandLine[1024]; if (argc < 2) return 0; _tcscpy(szCommandLine, argv[1]); for (int i = 2; i < argc; ++i) { _tcscat(szCommandLine, _T(" ")); _tcscat(szCommandLine, argv[i]); } /* szCommandLine now contains the command line arguments */ return 0; } 

(省略了错误检查)此代码适用于字符集的所有三种情况,因为我们使用TCHARtchar.h字符串函数和字符串文字_T 。 在编写此类TCHAR程序时,忘记用_T(..)包围字符串文字是编译器错误的常见原因。 如果我们没有完成所有这些事情,那么切换字符集会导致代码无法编译,或者更糟糕的是,编译但在运行时期间行为exception。

Windows API函数

对字符串起作用的Windows API函数(如CreateFileGetCurrentDirectory在Windows标头中实现为宏,与tchar.h宏一样,切换到“A”版本或“W”版本。 例如, CreateFile是一个宏,它定义为ANSI和MBCS的CreateFileW ,以及Unicode的CreateFileW

每当您在代码中使用平面forms(没有“A”或“W”)时,调用的实际函数将根据所选字符集进行切换。 您可以使用显式的“A”或“W”名称强制使用特定版本。

结论是您应该始终使用非限定名称,除非您想要始终引用特定版本,而与字符集选项无关。

对于问题中的示例,我们要打开第一个参数给出的文件:

 int _tmain(int argc, _TCHAR *argv[]) { if (argc < 2) return 1; HANDLE hFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); /* Read from file and do other stuff */ ... CloseHandle(hFile); return 0; } 

(省略错误检查)请注意,对于此示例,我们无处需要使用任何特定于TCHAR东西,因为宏定义已经为我们处理了这个问题。

利用C ++字符串

我们已经看到了如何使用tchar.h例程来使用C样式的字符串操作来处理TCHAR ,但是如果我们可以利用C ++ string来处理它,那将会很好。

我的建议最重要的是不使用TCHAR而是直接使用Unicode,请参阅结论部分,但如果您想使用TCHAR ,则可以执行以下操作。

要使用TCHAR ,我们想要的是使用TCHARstd::basic_string的实例。 您可以通过typedef自己的tstring来完成此操作:

 typedef std::basic_string tstring; 

对于字符串文字,不要忘记使用_T

您还需要使用正确版本的cincout 。 您可以使用引用来实现tcintcout

 #if defined(_UNICODE) std::wistream &tcin = wcin; std::wostream &tcout = wcout; #else std::istream &tcin = cin; std::ostream &tcout = cout; #end 

这应该可以让你做几乎任何事情。 可能偶尔会出现exception,例如std::to_stringstd::to_wstring ,您可以找到类似的解决方法。

结论

这个答案(希望如此)详细说明了TCHAR是什么以及它如何与Visual Studio和Windows标头一起使用和交织。 但是,我们也应该想知道我们是否想要使用它。

我的建议是直接对所有新的Windows程序使用Unicode,而根本不使用TCHAR

其他人提出同样的建议: TCHAR仍然具有相关性吗?

要在创建新项目后使用Unicode,请首先确保将字符集设置为Unicode。 然后,从源文件(或从stdafx.h )中删除#include 。 修复任何TCHAR_TCHARwchar_t_tmainwmain

 int wmain(int argc, wchar_t *argv[]) 

对于非控制台项目,Windows应用程序的入口点是WinMain ,并将显示在TCHAR -jargon中

 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) 

而且应该成为

 int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) 

在此之后,仅使用wchar_t字符串和/或std::wstring

进一步的警告

  • 在使用TCHAR数组(字符串)时写入sizeof(szMyString)时要小心,因为对于ANSI,这是字符和字节的大小,对于Unicode,这只是字节大小,字符数最多为一半,对于MBCS,这是以字节为单位的大小,字符数可能相等也可能不相等。 Unicode和MBCS都可以使用多个TCHAR来编码单个字符。
  • 混合TCHAR东西和固定charwchar_t是非常烦人的; 你必须使用正确的代码页将字符串从一个转换为另一个! 简单副本在一般情况下不起作用。
  • _UNICODEUNICODE之间存在细微差别,如果要有条件地定义自己的函数,则相关。 请参阅为什么UNICODE和_UNICODE?

一个非常好的,互补的答案是: Windows上的MBCS和UTF-8之间的差异