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

What are TCHAR strings and the 'A' or 'W' version of Win32 API functions?

什么是 TCHAR 字符串,例如 LPTSTRLPCTSTR,我该如何使用它们?当我在 Visual Studio 中创建一个新项目时,它会为我创建此代码:

#include <tchar.h>

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

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

如果我想用第一个命令行参数给定的名称打开一个文件,我该怎么做? Windows API 定义了其许多函数的 'A' 和 'W' 版本,例如 CreateFileCreateFileACreateFileW;那么它们之间有何不同,我应该使用哪一个?

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

字符集

我们首先需要了解的是字符集在Visual Studio中是如何工作的。项目 属性 页面有一个选项 select 使用的字符集:

  • 未设置
  • 使用 Unicode 字符集
  • 使用Multi-Byte字符集

根据您选择的三个选项中的哪一个,许多定义会发生变化以适应 selected 字符集。主要有三个 类:字符串、来自 tchar.h 的字符串例程和 API 函数:

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

相关阅读:About the "Character set" option in visual studio 2010

TCHAR.hheader

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

通过将所有 _txxx 符号定义为计算正确函数的宏来实现切换,具体取决于编译器切换。

其背后的想法是,您可以使用 encoding-agnostic 类型 TCHAR(或 _TCHAR)和适用于它们的 encoding-agnostic 函数,来自 tchar.h,而不是 string.h.

中的常规字符串函数

类似地,_tmain 被定义为 mainwmain。另见:What is the difference between _tmain() and main() in C++?

定义了一个辅助宏 _T(..) 以获取正确类型的字符串文字,"regular literals"L"wchar_t literals"

请参阅此处提到的注意事项:Is TCHAR still relevant? -- dan04's answer

_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(..) 包围字符串文字是编译器错误的常见来源。 如果我们没有做所有这些事情,那么切换字符集将导致代码无法编译,或者更糟的是,编译但在运行时行为不正常。

Windows API 函数

Windows API 作用于字符串的函数,例如 CreateFileGetCurrentDirectory,在 Windows header 中实现作为宏,就像 tchar.h 宏一样,切换到 'A' 版本或 'W' 版本。例如,CreateFile 是一个宏,对于 ANSI 和 MBCS 定义为 CreateFileA,对于 Unicode 定义为 CreateFileW

每当您在代码中使用平面形式(没有 'A' 或 'W')时,实际调用的函数将根据 selected 字符集进行切换。您可以通过使用明确的 '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 风格的字符串操作来处理 TCHARs,但是如果我们可以利用 C++ strings 来处理这个。

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

要使用TCHAR,我们需要的是使用TCHARstd::basic_string实例。您可以通过 typedefing 您自己的 tstring:

typedef std::basic_string<TCHAR> 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

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

结论

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

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

其他人给出同样的建议:Is TCHAR still relevant?

新建项目后要使用Unicode,首先确保字符集设置为Unicode。然后,从源文件(或 stdafx.h)中删除 #include <tchar.h>。将 TCHAR_TCHAR 修正为 wchar_t 并将 _tmain 修正为 wmain:

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

对于 non-console 项目,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个字符串and/orstd::wstrings.

进一步注意事项

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

一个很好的补充答案是:Difference between MBCS and UTF-8 on Windows