什么是TCHAR字符串和Win32 API函数的“A”或“W”版本?
什么是TCHAR
字符串,例如LPTSTR
和LPCTSTR
以及如何使用这些字符串? 当我在Visual Studio中创建一个新项目时,它为我创建了这个代码:
#include int _tmain(int argc, _TCHAR* argv[]) { return 0; }
例如,我如何连接所有命令行参数?
如果我想打开第一个命令行参数给出的名称的文件,我该怎么做? Windows API定义了许多函数的“A”和“W”版本,例如CreateFile
, CreateFileA
和CreateFileW
; 那么这些如何彼此不同以及我应该使用哪一个?
首先我要说的是,最好不要将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
被定义为main
或wmain
。 另请参阅: 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; }
(省略了错误检查)此代码适用于字符集的所有三种情况,因为我们使用TCHAR
, tchar.h
字符串函数和字符串文字_T
。 在编写此类TCHAR
程序时,忘记用_T(..)
包围字符串文字是编译器错误的常见原因。 如果我们没有完成所有这些事情,那么切换字符集会导致代码无法编译,或者更糟糕的是,编译但在运行时期间行为exception。
Windows API函数
对字符串起作用的Windows API函数(如CreateFile
和GetCurrentDirectory
在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
,我们想要的是使用TCHAR
的std::basic_string
的实例。 您可以通过typedef
自己的tstring
来完成此操作:
typedef std::basic_string tstring;
对于字符串文字,不要忘记使用_T
。
您还需要使用正确版本的cin
和cout
。 您可以使用引用来实现tcin
和tcout
:
#if defined(_UNICODE) std::wistream &tcin = wcin; std::wostream &tcout = wcout; #else std::istream &tcin = cin; std::ostream &tcout = cout; #end
这应该可以让你做几乎任何事情。 可能偶尔会出现exception,例如std::to_string
和std::to_wstring
,您可以找到类似的解决方法。
结论
这个答案(希望如此)详细说明了TCHAR
是什么以及它如何与Visual Studio和Windows标头一起使用和交织。 但是,我们也应该想知道我们是否想要使用它。
我的建议是直接对所有新的Windows程序使用Unicode,而根本不使用TCHAR
!
其他人提出同样的建议: TCHAR仍然具有相关性吗?
要在创建新项目后使用Unicode,请首先确保将字符集设置为Unicode。 然后,从源文件(或从stdafx.h
)中删除#include
。 修复任何TCHAR
或_TCHAR
到wchar_t
和_tmain
到wmain
:
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
东西和固定char
或wchar_t
是非常烦人的; 你必须使用正确的代码页将字符串从一个转换为另一个! 简单副本在一般情况下不起作用。 -
_UNICODE
和UNICODE
之间存在细微差别,如果要有条件地定义自己的函数,则相关。 请参阅为什么UNICODE和_UNICODE?
一个非常好的,互补的答案是: Windows上的MBCS和UTF-8之间的差异