繁体   English   中英

如何在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。

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

我已经使用UTF-8 BOM保存了UTF-8格式的文件。 但是使用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为我编译的方法。 请注意,我在源文件中使用的区域设置无关紧要。 似乎没有一个语言环境说“我知道我在做什么,所以不要f $%## ng更改我的字符串文字”。 特别是,无用的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在这个例子中试图太聪明了。

更新:

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

相反,我相信你只需要咬住子弹并将所有文本移动到数据文件中并从那里加载它。 我现在通过SimpleIni (跨平台的INI文件库)存储和访问INI文件中的文本。 至少可以保证它的工作原理是所有文本都不在程序之内。

原版的:

我自己回答这个问题,因为只有Evan才明白这个问题。 关于Unicode是什么以及如何使用wchar_t的答案与此问题无关,因为这不是关于国际化,也不是对Unicode,字符编码的误解。 我感谢您尝试提供帮助,但如果我不够清楚,我会道歉。

问题是我有源文件需要在各种平台和编译器下进行交叉编译。 该程序执行UTF-8处理。 它不关心任何其他编码。 我想在UTF-8中使用字符串文字,就像目前使用gcc和vc2003一样。 我如何用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 +以外的所有编译器上避免转换成本,如下所示。

#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)");

Brofield,

我遇到了完全相同的问题,只是偶然发现了一个不需要将源字符串转换为宽字符并返回的解决方案:将源文件保存为UTF-8 而不签名,VC2008将不管它。 当我想出丢掉签名时工作得很好。 总结一下:

Unicode(没有签名的UTF-8) - 代码页65001,不会在VC2008中抛出c4566警告并且不会导致VC混乱编码,而代码页65001(带签名的UTF-8)会抛出c4566(因为你有找到)。

希望现在为您提供帮助,但它可能会加快您的VC2008应用程序以取消您的解决方法。

虽然使用宽字符串然后根据需要转换为UTF-8可能更好。 我认为你最好的选择是如你所提到的那样在字符串中使用十六进制转义符。 就像假设你想要代码点\중 ,你可以这样做。

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

我相信这会很好用,只是不太可读,所以如果你这样做,请评论它解释。

文件/高级保存选项/编码:“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前缀

独立地,EDITOR可以自动检测没有BOM的UTF-8文件作为UTF-8文件。

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

#pragma execution_character_set("utf-8") 

它需要Visual Studio 2008 SP1和以下修补程序:

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

这个怎么样? 将字符串存储在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。

使用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的返程应该同样容易,但我发现UTF-8语言环境在我的系统上无法正常工作(Win7上的VC10 Express)。

因此,我写了一个基于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;
}

我相信这应该可以在任何平台上运行,但除了在我自己的系统上,我还无法测试它,所以它可能有bug。

#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;
}

也许尝试一下实验:

#pragma setlocale(".UTF-8")

要么:

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

我遇到了类似的问题。 我的UTF-8字符串文字在编译期间被转换为当前的系统代码页 - 我只是在hex-viewer中打开了.obj文件,它们已经被破坏了。 例如,角色ć只是一个字节。

我的解决方案是以UTF-8和没有物料清单保存。 这就是我欺骗编译器的方式。 它现在认为这只是一个普通的来源,并不会翻译字符串。 在.obj文件中ć现在是两个字节。

请忽略一些评论员。 我明白你想要什么 - 我也想要一样:UTF-8源,UTF-8生成的文件,UTF-8输入文件,UTF-8通过通信线路而不用翻译。

也许这有助于......

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

要正确编译UTF-8源文件,必须以UTF-8保存而不使用BOM编码, 系统区域设置(非Unicode语言)必须为英语

在此输入图像描述

UTF-8源文件

  • 没有BOM :被视为原始,除非您的系统使用> 1byte / char代码页(如Shift JIS)。 您需要将系统代码页更改为任何单个字节,然后您应该能够在文字内使用Unicode字符并编译而不会出现问题(至少我希望如此)。
  • 使用BOM :在编译期间将char和字符串文字转换为系统代码页。 您可以使用GetACP()检查当前系统代码页。 AFAIK,无法将系统代码页设置为65001(UTF-8),因此无法直接使用带有BOM的UTF-8。

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

我有一个类似的问题编译UTF-8窄(字符)字符串文字,我发现基本上我必须有UTF-8 BOM和#pragma execution_character_set("utf-8") [1],或者两者都没有也不是实用主义[2]。 使用一个没有另一个导致不正确的转换。

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

[1]:Visual Studio 2012不支持execution_character_set。 Visual Studio 2010和2015它运行良好,如你所知,在2008年的补丁它工作正常。

[2]:该线程中的一些注释指出,既不使用BOM也不使用pragma可能会导致使用多字节本地代码页(例如日本)的开发人员进行不正确的转换。

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

首先,你应该在Single Byte Code Page Local(如英语)下运行,这样cl.exe就不会让代码变得混乱。

二,保存源代码的UTF8-NO BOM,请注意,NO-BOM,然后用cl.exe编译,不要调用任何C API,比如printf wprint,所有这些工作人员都不工作,我不知道我知道为什么:)....之后可能会有一项研究......

然后只需编译运行,你会看到结果.....我的邮箱是罗永刚,(谷歌的)希望有些......

WScript的:

#! /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

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:

//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\n", requiredSize, buffer);
        buffer[requiredSize] = 0;
        MultiByteToWideChar(CP_UTF8, 0, cstr, -1, buffer, requiredSize);
        printf("Finished\n");
        return buffer;
    }
    printf("Convert failed\n");
    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\n");
    for (i = 0; i < strlen(str); ++i)
    {
        printf("%02x ", (unsigned char)str[i]);
    }

    printf("\nUTF16 sequence\n");
    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)");
}

我遇到了类似的问题,解决方案是使用高级保存选项以UTF8保存

阅读文章。 首先,你不需要UTF-8。 UTF-8只是一种表示字符的方式。 你想要宽字符(wchar_t)。 你把它们写成L“yourtextgoeshere”。 该文字的类型是wchar_t *。 如果你赶时间,请查看wprintf。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM