如何在Visual C ++ 2008中创建UTF-8字符串文字

How to create a UTF-8 string literal in Visual C++ 2008

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

1
2
3
4
printf("Chinese (Traditional)");
printf("中国語 (繁体)");
printf("??? (??)");
printf("Chinês (Tradicional)");

我已使用UTF-8 BOM将文件保存为UTF-8格式。但是,使用VC2008进行编译会导致:

1
2
3
4
5
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为我编译此方法的方法。请注意,在源文件中使用哪种语言环境都没有关系。似乎没有一个显示"我知道我在做什么,所以不要f $%## ng更改我的字符串文字"的语言环境。特别是,无用的UTF-8伪语言环境不起作用。

1
2
#pragma setlocale(".65001")
=> error C2175: '.65001' : invalid locale

" C"也不:

1
2
#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在这种情况下会变得过于聪明。


更新:

我已经决定没有保证的方法可以做到这一点。我在下面介绍的解决方案适用于英文版VC2003,但是在使用日语版VC2003(或也许是日语OS)进行编译时会失败。无论如何,都不能依靠它来工作。请注意,即使将所有内容声明为L"字符串也不起作用(并且在gcc中造成痛苦,如下所述)。

相反,我认为您只需要咬一下子弹并将所有文本移动到数据文件中并从那里加载它。我现在通过SimpleIni(跨平台INI文件库)在INI文件中存储和访问文本。至少可以保证它可以正常工作,因为所有文本都在程序之外。

原版的:

我自己回答这个问题,因为只有埃文(Evan)似乎了解这个问题。关于Unicode是什么以及如何使用wchar_t的答案与该问题无关,因为这与国际化无关,也与对Unicode字符编码的误解无关。我很感谢您为我提供帮助的尝试,如果我不太清楚,我深表歉意。

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

这是我发现的:

gcc(v4.3.2 20081105):

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

vc2003:

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

vc2005 +:

  • 字符串文字由编译器处理(没有原始字符串)
  • 将char字符串文字重新编码为指定的语言环境
  • 不支持将UTF-8用作目标语言环境
  • 源文件必须具有UTF-8 BOM

因此,简单的答案是,出于该特定目的,VC2005 +已损坏并且不提供向后兼容的编译路径。将Unicode字符串输入已编译程序的唯一方法是通过UTF-8 + BOM + wchar,这意味着我需要在使用时将所有字符串都转换回UTF-8。

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

最后,我决定避免使用vc2005 +以外的所有编译器,并使用如下所示的源来进行转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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上的测试中正常运行:

1
2
3
4
5
std::string mText;
mText = utf8("Chinese (Traditional)");
mText = utf8("中国語 (繁体)");
mText = utf8("??? (??)");
mText = utf8("Chinês (Tradicional)");


虽然最好使用宽字符串,然后根据需要将其转换为UTF-8。我认为您最好的选择是像您提到的那样在字符串中使用十六进制转义符。就像假设您想要代码点\uC911一样,您可以执行此操作。

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

我相信这会很好用,只是不太可读,因此,如果您这样做,请发表评论以解释。


布罗菲尔德,

我遇到了完全相同的问题,只是偶然发现了一个不需要将源字符串转换为宽字符并返回的解决方案:将源文件另存为UTF-8,无需签名,而VC2008会将其保留下来。当我确定放弃签名时,效果很好。总结一下:

Unicode(无签名的UTF-8)-代码页65001,在VC2008中不会引发c4566警告,也不会导致VC弄乱编码,而代码页65001(带有签名的UTF-8)确实会引发c4566(如您所愿)找到)。

希望为时不晚,对您有所帮助,但它可能会加快VC2008应用程序的运行速度,以消除您的解决方法。


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


源文件的Visual C ++(2005+)COMPILER标准行为是:

  • CP1252(对于此示例,西欧代码页):

    • "?"C4 00
    • '?'C4
    • L"?"00C4 0000
    • L'?'00C4
  • 不带BOM的UTF-8:

    • "?"C3 84 00(= UTF-8)
    • '?'→警告:多字符常量
    • "?"E2 84 A6 00(= UTF-8,如预期)
    • L"A"00C3 0084 0000(错!)
    • L'?'→警告:多字符常量
    • L"?"00E2 0084 00A6 0000(错!)
  • 具有BOM的UTF-8:

    • "?"C4 00(= CP1252,不再有UTF-8),
    • '?'C4
    • "?"→错误:无法转换为CP1252!
    • L"?"00C4 0000(正确)
    • L'?'00C4
    • L"?"2126 0000(正确)

可以看到,C编译器在不使用BOM的情况下处理UTF-8文件的方式与CP1252相同。结果,编译器不可能将UTF-8和UTF-16字符串混合到已编译的输出中!因此,您必须决定一个源代码文件:

  • 要么将UTF-8与BOM一起使用并仅生成UTF-16字符串(即始终使用L前缀),
  • 或不带BOM的UTF-8,并且仅生成UTF-8字符串(即永远不要使用L前缀)。
  • 不涉及7位ASCII字符,可以使用带或不带L前缀的字符

独立地,编辑者可以自动检测UTF-8文件,而无需将BOM作为UTF-8文件。


从评论到这个非常好的博客
"使用UTF-8作为Visual Studio在C和C ++中字符串的内部表示形式"
=> http://www.nubaria.com/en/blog/?p=289

1
#pragma execution_character_set("utf-8")

It requires Visual Studio 2008 SP1, and the following hotfix:

http://support.microsoft.com/kb/980263
....


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

1
"中国語 (繁体)"

转换为

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

当然,这是任何人都无法理解的,其目的仅仅是为了避免编译器出现问题。

您可以使用C ++预处理程序引用转换后的头文件中的字符串,也可以在使用此技巧进行编译之前将整个UTF-8源转换为ASCII。


使用char_traits :: widen()可轻松进行任何本地编码的可移植转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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的返回旅程应该同样容易,但是我发现UTF-8语言环境在我的系统上无法正常工作(Win7上的VC10 Express)。

因此,我写了一个基于RFC 3629的简单转换器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/////////////////////////////////////////////////////////
// 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;
}

我相信这可以在任何平台上使用,但是除了我自己的系统之外,我无法对其进行测试,因此它可能存在错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <fstream>

int main()
{
    const char szTest[] ="Das tausendsch?ne Jungfr?ulein,
"

                         "Das tausendsch?ne Herzelein,
"

                         "Wollte Gott, wollte Gott,
"

                         "ich w?r' heute bei ihr!
"
;

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

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


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

对我来说,解决方案是保存为UTF-8和WITH BOM。这就是我欺骗编译器的方式。现在,它认为这只是正常的来源,不会翻译字符串。在.obj文件中?现在是两个字节。

请忽略一些评论员。我了解您想要的-我也想要同样的东西:UTF-8源代码,UTF-8生成的文件,UTF-8输入文件,通过通信线路的UTF-8,而无需翻译。

也许这有帮助...


我知道我参加聚会迟到了,但是我认为我需要把它传播出去。对于Visual C ++ 2005及更高版本,如果源文件不包含BOM(字节顺序标记),并且您的系统区域设置不是英语,则VC会假定您的源文件不是Unicode。

为了正确编译您的UTF-8源文件,您必须将其保存为不带BOM编码的UTF-8,并且系统区域设置(非Unicode语言)必须为英语。

enter image description here


也许尝试一个实验:

1
#pragma setlocale(".UTF-8")

要么:

1
#pragma setlocale("english_england.UTF-8")


我有一个类似的问题,解决方案是使用高级保存选项在UTF8中保存


我在编译UTF-8窄(字符)字符串文字时遇到了类似的问题,我发现基本上我必须同时拥有UTF-8 BOM和#pragma execution_character_set("utf-8") [1],或者既没有BOM又没有杂注[2]。不使用另一个而导致转换错误。

我在https://github.com/jay/compiler_string_test中记录了详细信息

[1]:Visual Studio 2012不支持execution_character_set。 Visual Studio 2010和2015可以正常运行,并且您知道2008年的补丁程序可以正常运行。

[2]:此主题中的一些评论已指出,对于使用多字节本地代码页(例如,日本)的开发人员,既不使用BOM也不使用编译指示可能会导致错误的转换。


所以,事情要改变。
现在我有了解决方案。

首先,您应该在本地的单字节代码页(例如英语)下运行,以使cl.exe不会使代码变得混乱。

其次,将源代码保存在UTF8-NO BOM中,请注意NO-BOM,然后用
cl.exe,请勿调用任何C API,例如printf wprint,所有这些人员均无法正常工作,我不知道为什么:)....稍后可能需要学习...

然后只需编译并运行,您将看到结果.....
我的电子邮件是罗永刚(Google的希望)

wscript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#! /usr/bin/env python
# encoding: utf-8
# Yonggang Luo

# the following two variables are used by the target"waf dist"
VERSION='0.0.1'
APPNAME='cc_test'

top = '.'

import waflib.Configure

def options(opt):
    opt.load('compiler_c')

def configure(conf):
    conf.load('compiler_c')
    conf.check_lib_msvc('gdi32')
    conf.check_libs_msvc('kernel32 user32')

def build(bld):
    bld.program(
        features = 'c',
        source   = 'chinese-utf8-no-bom.c',
        includes = '. ..',
        cflags   = ['/wd4819'],
        target   = 'myprogram',
        use      = 'KERNEL32 USER32 GDI32')

运行脚本run.bat

1
2
3
4
5
6
7
8
9
10
11
rd /s /q build
waf configure build --msvc_version"msvc 6.0"
build\myprogram

rd /s /q build
waf configure build --msvc_version"msvc 9.0"
build\myprogram

rd /s /q build
waf configure build --msvc_version"msvc 10.0"
build\myprogram

源代码main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//encoding : utf8 no-bom
#include <stdio.h>
#include <string.h>

#include <Windows.h>

char* ConvertFromUtf16ToUtf8(const wchar_t *wstr)
{
    int requiredSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, 0, 0, 0, 0);
    if(requiredSize > 0)
    {
        char *buffer = malloc(requiredSize + 1);
        buffer[requiredSize] = 0;
        WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, requiredSize, 0, 0);
        return buffer;
    }
    return NULL;
}

wchar_t* ConvertFromUtf8ToUtf16(const char *cstr)
{
    int requiredSize = MultiByteToWideChar(CP_UTF8, 0, cstr, -1, 0, 0);
    if(requiredSize > 0)
    {
        wchar_t *buffer = malloc( (requiredSize + 1) * sizeof(wchar_t) );
        printf("converted size is %d 0x%x
"
, requiredSize, buffer);
        buffer[requiredSize] = 0;
        MultiByteToWideChar(CP_UTF8, 0, cstr, -1, buffer, requiredSize);
        printf("Finished
"
);
        return buffer;
    }
    printf("Convert failed
"
);
    return NULL;
}

void ShowUtf8LiteralString(char const *name, char const *str)
{
    int i = 0;
    wchar_t *name_w = ConvertFromUtf8ToUtf16(name);
    wchar_t *str_w = ConvertFromUtf8ToUtf16(str);

    printf("UTF8 sequence
"
);
    for (i = 0; i < strlen(str); ++i)
    {
        printf("%02x", (unsigned char)str[i]);
    }

    printf("
UTF16 sequence
"
);
    for (i = 0; i < wcslen(str_w); ++i)
    {
        printf("%04x", str_w[i]);
    }

    //Why not using printf or wprintf? Just because they do not working:)
    MessageBoxW(NULL, str_w, name_w, MB_OK);
    free(name_w);
    free(str_w);

}

int main()
{
    ShowUtf8LiteralString("English english_c","Chinese (Traditional)");
    ShowUtf8LiteralString("简体 s_chinese_c","你好世界");
    ShowUtf8LiteralString("繁体 t_chinese_c","中国語 (繁体)");
    ShowUtf8LiteralString("Korea korea_c","??? (??)");
    ShowUtf8LiteralString("What? what_c","Chinês (Tradicional)");
}


UTF-8源文件

  • 不带BOM:除非系统使用的是> 1byte / char代码页(例如Shift JIS),否则将被视为原始数据。您需要将系统代码页更改为任意一个字节,然后应该能够在文字中使用Unicode字符并进行编译而不会出现问题(至少我希望如此)。
  • 使用BOM:在编译过程中是否将char和string文字转换为系统代码页。您可以使用GetACP()检查当前系统代码页。 AFAIK,无法将系统代码页设置为65001(UTF-8),因此无法将BOM表直接使用UTF-8。

唯一可移植且与编译器无关的方式是使用ASCII字符集和转义序列,因为不能保证任何编译器都会接受UTF-8编码的文件。


我同意西奥·沃斯(Theo Vosse)的观点。阅读Joel On Software上的文章绝对绝对,绝对是每个软件开发人员绝对肯定要了解的Unicode和字符集(无借口!)...


阅读文章。首先,您不需要UTF-8。 UTF-8只是表示字符的一种方式。您需要宽字符(wchar_t)。您将它们记为L" yourtextgoeshere"。该文字的类型为wchar_t *。如果您很着急,请查找wprintf。