如何在Visual C++ 2008中创建一个UTF-8字符串字面量

69

在VC++ 2003中,我只需将源文件保存为UTF-8,所有字符串就会按原样使用。换句话说,以下代码将字符串按原样打印到控制台。如果源文件保存为UTF-8,则输出将为UTF-8。

printf("Chinese (Traditional)");
printf("中国語 (繁体)");
printf("중국어 (번체)");
printf("Chinês (Tradicional)");

我已经用UTF-8 BOM的格式保存了文件。但是在使用VC2008编译时会出现以下问题:

warning C4566: character represented by universal-character-name '\uC911' 
cannot be represented in the current code page (932)
warning C4566: character represented by universal-character-name '\uAD6D' 
cannot be represented in the current code page (932)
etc.
这些警告所涉及的字符已经损坏。适合区域设置(在这种情况下为932 = 日语)的字符会被转换为该区域设置的编码方式,即Shift-JIS。
我找不到让VC++ 2008为我编译这个的方法。请注意,在源文件中使用哪种区域设置都无关紧要。似乎没有一种区域设置可以说“我知道我在做什么,所以不要改变我的字符串文字”。特别是,无用的UTF-8伪区域设置不起作用。
#pragma setlocale(".65001") 
=> error C2175: '.65001' : invalid locale

"C"也不行:

#pragma setlocale("C") 
=> see warnings above (in particular locale is still 932)

看起来 VC2008 强制所有字符进入指定(或默认)语言环境,而该语言环境不能是 UTF-8。我不想更改文件以使用转义字符串如 "\xbf\x11...",因为相同的源代码使用 gcc 编译可以轻松处理 UTF-8 文件。

有没有办法指定源文件的编译应该保持字符串文字不变?

换句话说,有哪些编译标志可以用来指定与 VC2003 的向后兼容性,当编译源文件时不更改字符串文字,按字节使用它们就像它们本来一样。

更新

感谢您的建议,但我想避免使用wchar。由于这个应用程序专门处理UTF-8字符串,所以使用wchar将要求我将所有字符串转换回UTF-8,这应该是不必要的。所有输入、输出和内部处理都是在UTF-8中进行的。这是一个简单的应用程序,在Linux上工作良好,并且使用VC2003编译时也可以正常工作。我希望能够使用VC2008编译相同的应用程序并使其正常工作。

为了实现这一点,我需要VC2008不尝试将其转换为我的本地机器的语言环境(日语,932)。我希望VC2008与VC2003向后兼容。我希望有一个语言环境或编译器设置,可以将字符串用作它们本来的样子,本质上是char数组或UTF-8。看起来我可能被卡在VC2003和gcc上了,VC2008在这种情况下试图太聪明了。


1
请参考新版VS的答案:https://dev59.com/8nnZa4cB1Zd3GeqPmiYX - bames53
我在VS 2012中遇到了同样的问题。 它破坏了我的UTF-8编码。为什么VS如此不稳定? 我想我会把我的数据放在一个外部文件中,这样它就不会被搞砸了。需要绕过有缺陷的编译器真是太烦人了。 另外,C++11允许将编码指定为u8"文字",但VS 2012不支持该功能,因此无济于事。 - Joe
18个回答

33

更新:

我决定没有可靠的方法来解决这个问题。下面提出的解决方案适用于英文版VC2003,但在编译日文版VC2003时失败(或者可能是日文操作系统)。无论如何,不能依赖它工作。请注意,即使将所有内容声明为L""字符串也不起作用(在gcc中描述痛苦)。

相反,我认为您只需要咬紧牙关,将所有文本移到数据文件中,并从那里加载它。我现在正在通过SimpleIni(跨平台INI文件库)存储和访问文本。至少有一个保证,它是有效的,因为所有文本都不在程序中。

原始内容:

我自己回答这个问题,因为似乎只有Evan理解了问题。关于Unicode是什么以及如何使用wchar_t的答案与此问题无关,因为这不涉及国际化,也不涉及对Unicode、字符编码的误解。非常感谢您的帮助,如果我表述不清楚,还请见谅。

问题是我有一些源文件需要在各种平台和编译器下进行交叉编译。该程序进行UTF-8处理。它不关心任何其他编码方式。我想像目前在gcc和vc2003中使用UTF-8的字符串字面量一样使用UTF-8字符串字面量。如何在VC2008中实现它?(即向后兼容的解决方案)。

这是我找到的:

gcc(v4.3.2 20081105):

  • 字符串字面量按原样使用(原始字符串)
  • 支持UTF-8编码的源文件
  • 源文件不能有UTF-8 BOM

vc2003:

  • 字符串字面量按原样使用(原始字符串)
  • 支持UTF-8编码的源文件
  • 源文件可以有或没有UTF-8 BOM(都没关系)

vc2005+:

  • 编译器会处理字符串文字(没有原始字符串)
  • 字符字符串文字将被重新编码为指定的区域设置
  • 不支持以UTF-8作为目标语言环境
  • 源文件必须具有UTF-8 BOM
  • 所以,简单来说,对于这个特定的目的,VC2005+是有缺陷的,没有提供向后兼容的编译路径。将Unicode字符串传递到编译后的程序中的唯一方法是使用UTF-8 + BOM + wchar,这意味着我需要在使用时将所有字符串转换回UTF-8。

    并没有任何简单的跨平台方法将wchar转换为UTF-8,例如,wchar的大小和编码是什么?在Windows上是UTF-16,在其他平台上呢?这是不同的。请参见ICU项目获取更多细节信息。

    最终,我决定除了vc2005 +之外的所有编译器都避免转换成本,并使用以下源代码。

    #if defined(_MSC_VER) && _MSC_VER > 1310
    // Visual C++ 2005 and later require the source files in UTF-8, and all strings 
    // to be encoded as wchar_t otherwise the strings will be converted into the 
    // local multibyte encoding and cause errors. To use a wchar_t as UTF-8, these 
    // strings then need to be convert back to UTF-8. This function is just a rough 
    // example of how to do this.
    # define utf8(str)  ConvertToUTF8(L##str)
    const char * ConvertToUTF8(const wchar_t * pStr) {
        static char szBuf[1024];
        WideCharToMultiByte(CP_UTF8, 0, pStr, -1, szBuf, sizeof(szBuf), NULL, NULL);
        return szBuf;
    }
    #else
    // Visual C++ 2003 and gcc will use the string literals as is, so the files 
    // should be saved as UTF-8. gcc requires the files to not have a UTF-8 BOM.
    # define utf8(str)  str
    #endif
    
    请注意,这段代码只是一个简化的例子。在生产环境中,需要进行多种方式的清理(线程安全、错误检查、缓冲区大小检查等)。
    以下是使用方法。它能够在gcc、vc2003和vc2008编译器上成功编译并正确运行。
    std::string mText;
    mText = utf8("Chinese (Traditional)");
    mText = utf8("中国語 (繁体)");
    mText = utf8("중국어 (번체)");
    mText = utf8("Chinês (Tradicional)");
    

非常好的答案和解决方案,谢谢。 - TinyRacoon
2
运行时转换,你在开玩笑吧?只需向下滚动(链接:https://dev59.com/FHRB5IYBdhLWcg3wNk93#2411769),就能找到一个更简单的编译时解决方案(只需将源代码保存为UTF-8格式)。 - rustyx

17

尽管最好使用宽字符串,然后根据需要转换为UTF-8。但我认为你最好像你所提到的那样,在字符串中使用十六进制转义。假设你想要代码点\uC911,你可以这样做。

const char *str = "\xEC\xA4\x91";

我相信这个方法可以正常工作,只是不够易读。因此,如果您使用这种方法,请添加注释以便解释。


2
+1,但我更喜欢使用三位八进制转义而不是十六进制转义,因为十六进制转义采用最大匹配规则。例如,“Ond bråd död i Venedig”在八进制中为:“"Ond bra\314\212d do\314\210d i Venedig"”,在十六进制中为:“"Ond bra\xCC\x8A""d do\xCC\x88""d i Venedig"”。 - dalle
这不是UTF-8中的\uC911表示。 - Lev
@Lev:好的,没问题,我会修复的。 - Evan Teran
我被这个问题困扰了一段时间。我在输入代码点表示时只使用了一个 \x。至少在我的系统上,我必须输入最后两个十六进制数字才能显示符号(例如对于 U+25A1 这样的字符,\xA1 可以工作)。 - Artorias2718

16

Brofield,

我曾经遇到过完全相同的问题,但是我偶然发现了一个解决方案,它不需要将源字符串转换为宽字符再转回来:将源文件保存为UTF-8格式,不要带有签名,这样VC2008就不会对其进行更改。当我想到去掉签名时,它运行得非常好。总结一下:

Unicode(UTF-8无签名) - 代码页65001,在VC2008中不会引发c4566警告,并且不会导致VC混淆编码,而代码页65001(带签名的UTF-8)会引发c4566(正如您所发现的那样)。

希望这个解决方案能够帮到你,如果你采用这种方法,可能会加快你的VC2008应用程序的速度。


1
-1,对我没用(英文VS2010)。我收到了C4819警告和许多其他语法错误。我在韩国工作,但我的操作系统设置为以韩语Unicode字符串显示。 - Verax
如果文件保存为“Unicode(UTF-8无签名) - 代码页65001”,类似于std :: string jp1 =“てすと”;这样的代码可以在VS2012中运行。 - Echsecutor
这个解决方案对我在英文VS2015中使用韩文字母有效。 - Sumyrda - remember Monica

14

文件/高级保存选项/编码: "Unicode (UTF-8无签名) - 代码页 65001"


3
尝试使用日语版编译器进行编译。 - brofield
3
你说“没有签名”就不起作用,这很奇怪,因为编译器在没有进行额外处理的情况下无法识别输入为UTF-8格式。你说日语版本会执行这样的逻辑,非常有趣。但是这个技巧仍然适用于俄语。 - Vladius
1
这个技巧显然适用于任何保留ASCII部分的编码。也就是说,UTF-8、ISO-8859-x、KOI8-R和其他许多编码方式都可以使用。 - jmster
2020年更新:没有签名保存仍然可以让Visual Studio编译器正确地将字面字符串转换为UTF-8编码的const char *。带有签名的保存仍会导致Visual Studio将字面字符串解释为wchar_t。 - Medran

10

Visual C++ (2005+)编译器对源代码文件的标准行为如下:

  • CP1252(以西欧码页为例):
    • "Ä"C4 00
    • 'Ä'C4
    • L"Ä"00C4 0000
    • L'Ä'00C4
  • UTF-8无BOM:
    • "Ä"C3 84 00 (= UTF-8)
    • 'Ä' → 警告:多字符常量
    • "Ω"E2 84 A6 00 (= UTF-8,预期为此)
    • L"A"00C3 0084 0000 (错误!)
    • L'Ä' → 警告:多字符常量
    • L"Ω"00E2 0084 00A6 0000 (错误!)
  • UTF-8带有BOM:
    • "Ä"C4 00(= CP1252,不再是UTF-8)
    • 'Ä'C4
    • "Ω" → 错误:无法转换为CP1252!
    • L"Ä"00C4 0000(正确)
    • L'Ä'00C4
    • L"Ω"2126 0000(正确)

你知道,C编译器处理没有BOM的UTF-8文件的方式与CP1252相同。结果,编译器无法将UTF-8和UTF-16字符串混合到编译输出中!因此,您必须为一个源代码文件决定:

  • 要么使用带BOM的UTF-8,并仅生成UTF-16字符串(即始终使用L前缀)
  • 要么不使用BOM的UTF-8,并仅生成UTF-8字符串(即从不使用L前缀)。
  • 7位ASCII字符不受影响,可以使用或不使用L前缀

独立地,编辑器可以将没有BOM的UTF-8文件自动检测为UTF-8文件。


8

1
+1,尽管在VS2012中显然不支持此功能:http://connect.microsoft.com/VisualStudio/feedback/details/773186/pragma-execution-character-set-utf-8-didnt-support-in-vc-2012 - Josh Kelley

4

这样怎么样?您可以将字符串存储在UTF-8编码的文件中,然后预处理它们为ASCII编码的C++源文件。通过使用十六进制转义序列,您可以在字符串内部保留UTF-8编码。字符串

"中国語 (繁体)"

被转换为

"\xE4\xB8\xAD\xE5\x9B\xBD\xE8\xAA\x9E (\xE7\xB9\x81\xE4\xBD\x93)"

当然,这段内容对于任何人来说都是无法阅读的,目的只是为了避免编译器出现问题。

你可以使用C++预处理器引用转换后的头文件中的字符串,或者使用此技巧将整个UTF-8源代码转换为ASCII进行编译。


3
使用 char_traits::widen() 进行便携式转换是很简单的,无论你使用的是什么本地编码。
#include <locale>
#include <string>
#include <vector>

/////////////////////////////////////////////////////////
// NativeToUtf16 - Convert a string from the native 
//                 encoding to Unicode UTF-16
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::wstring NativeToUtf16(const std::string &sNative)
{
  std::locale locNative;

  // The UTF-16 will never be longer than the input string
  std::vector<wchar_t> vUtf16(1+sNative.length());

  // convert
  std::use_facet< std::ctype<wchar_t> >(locNative).widen(
        sNative.c_str(), 
        sNative.c_str()+sNative.length(), 
        &vUtf16[0]);

  return std::wstring(vUtf16.begin(), vUtf16.end());
}

理论上,从UTF-16到UTF-8的返回旅程应该同样简单,但我发现在我的系统(Win7上的VC10 Express)上,UTF-8语言环境无法正常工作。

因此,我根据RFC 3629编写了一个简单的转换器。

/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a character from UTF-16 
//                 encoding to UTF-8.
//                 NB: Does not handle Surrogate pairs.
//                     Does not test for badly formed 
//                     UTF-16
// Parameters:
//   chUtf16 (in): Input char
// Returns:        UTF-8 version as a string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(wchar_t chUtf16)
{
    // From RFC 3629
    // 0000 0000-0000 007F   0xxxxxxx
    // 0000 0080-0000 07FF   110xxxxx 10xxxxxx
    // 0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx

    // max output length is 3 bytes (plus one for Nul)
    unsigned char szUtf8[4] = "";

    if (chUtf16 < 0x80)
    {
        szUtf8[0] = static_cast<unsigned char>(chUtf16);
    }
    else if (chUtf16 < 0x7FF)
    {
        szUtf8[0] = static_cast<unsigned char>(0xC0 | ((chUtf16>>6)&0x1F));
        szUtf8[1] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }
    else
    {
        szUtf8[0] = static_cast<unsigned char>(0xE0 | ((chUtf16>>12)&0xF));
        szUtf8[1] = static_cast<unsigned char>(0x80 | ((chUtf16>>6)&0x3F));
        szUtf8[2] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }

    return reinterpret_cast<char *>(szUtf8);
}


/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a string from UTF-16 encoding
//                 to UTF-8
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(const std::wstring &sUtf16)
{
    std::string sUtf8;
    std::wstring::const_iterator itr;

    for (itr=sUtf16.begin(); itr!=sUtf16.end(); ++itr)
        sUtf8 += Utf16ToUtf8(*itr);
    return sUtf8;
}

我相信这个方法可以在任何平台上运行,但是除了我的系统之外,我还没有测试过它,所以可能存在错误。

#include <iostream>
#include <fstream>

int main()
{
    const char szTest[] = "Das tausendschöne Jungfräulein,\n"
                          "Das tausendschöne Herzelein,\n"
                          "Wollte Gott, wollte Gott,\n"
                          "ich wär' heute bei ihr!\n";

    std::wstring sUtf16 = NativeToUtf16(szTest);
    std::string  sUtf8  = Utf16ToUtf8(sUtf16);

    std::ofstream ofs("test.txt");
    if (ofs)
        ofs << sUtf8;
    return 0;
}

你完全错过了问题的要点。要求是从编译到处理过程中永远不要离开UTF-8。此外,Windows不支持UTF-8作为代码页(因为假定所有MBCS最多只有2个字节)。请参见http://blogs.msdn.com/b/michkap/archive/2007/01/03/1392379.aspx。 - brofield
那不是UTF-16,那是UCS-2。 - dalle

1

我遇到了类似的问题。我的UTF-8字符串文字在编译过程中被转换为当前系统的代码页 - 我只需要在十六进制查看器中打开.obj文件,它们就已经被破坏了。例如,字符ć只有一个字节。

对我来说,解决办法是保存为UTF-8且没有BOM(字节顺序标记)。这样我就欺骗了编译器,它现在认为这只是普通的源代码,不会再转换字符串。在.obj文件中,ć现在是两个字节。

请忽略一些评论者。我理解你想要什么 - 我也想要同样的:UTF-8源代码,生成的文件也是UTF-8格式,输入文件也是UTF-8格式,在通信线路上使用UTF-8而不进行任何转换。

也许这能帮到你...


很高兴它对你有用。我相信如果你使用非英语系统区域设置,那么在这条路线上会出现问题。我有一个日语编译器和日语系统区域设置,但这对我没有用,因为它似乎试图从Shift-JIS转换字符串文字,但由于它们是UTF-8,所以失败了。 - brofield

1
现在有一个名为 /utf-8 的编译器命令行选项来实现这个功能。
在 Visual Studio 开发环境中设置这个编译器选项的方法如下:
1. 打开项目的属性页面对话框。 2. 选择 配置属性 -> C/C++ -> 命令行 属性页。 3. 在 附加选项 中添加 /utf-8 选项以指定您首选的编码方式。 4. 点击确定保存更改。
了解更多信息,请参见: https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-160

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接