web-dev-qa-db-ja.com

Windowsコンソールでutf8文字を正しく印刷する

これは私がそれをやろうとする方法です:

#include <stdio.h>
#include <windows.h>
using namespace std;

int main() {
  SetConsoleOutputCP(CP_UTF8);
   //german chars won't appear
  char const* text = "aäbcdefghijklmnoöpqrsßtuüvwxyz";
  int len = MultiByteToWideChar(CP_UTF8, 0, text, -1, 0, 0);
  wchar_t *unicode_text = new wchar_t[len];
  MultiByteToWideChar(CP_UTF8, 0, text, -1, unicode_text, len);
  wprintf(L"%s", unicode_text);
}

そしてその効果は、私たちのASCII文字だけが表示されるということです。エラーは表示されません。ソースファイルはutf8でエンコードされています。

だから、私がここで間違っていることは何ですか?

wouterHへ:

int main() {
  SetConsoleOutputCP(CP_UTF8);
  const wchar_t *unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
  wprintf(L"%s", unicode_text);
}
  • これも機能しません。効果は同じです。私のフォントはもちろんLucidaConsoleです。

3番目のテイク:

#include <stdio.h>
#define _WIN32_WINNT 0x05010300
#include <windows.h>
#define _O_U16TEXT  0x20000
#include <fcntl.h>

using namespace std;

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);
    const wchar_t *u_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", u_text);
}

わかりました、何かが機能し始めますが、出力は次のとおりです:ańbcdefghijklmno÷pqrs▀tuŘvwxyz

16
rsk82

デフォルトでは、Windowsのワイド印刷機能はASCII範囲外の文字を処理しません。

UnicodeデータをWindowsコンソールに取得する方法はいくつかあります。

  • コンソールAPI、WriteConsoleWを直接使用します。実際にコンソールに書き込んでいることを確認し、出力が他の何かに向けられている場合は他の手段を使用する必要があります。

  • 標準出力ファイル記述子のモードを「Unicode」モードの1つ、_O_U16TEXTまたは_O_U8TEXTに設定します。これにより、ワイド文字出力関数がUnicodeデータをWindowsコンソールに正しく出力します。コンソールを表さないファイル記述子で使用された場合、バイトの出力ストリームはそれぞれUTF-16とUTF-8になります。 N.B.これらのモードを設定した後、対応するストリームの非ワイド文字関数は使用できなくなり、クラッシュします。ワイド文字関数のみを使用する必要があります。

  • 適切な関数を使用すれば、コンソール出力コードページをCP_UTF8に設定することにより、UTF-8テキストをコンソールに直接印刷できます。 basic_ostream<char>::operator<<(char*)などの高レベルの関数のほとんどはこのようには機能しませんが、低レベルの関数を使用するか、標準関数の問題を回避する独自のostreamを実装できます。

3番目の方法の問題はこれです:

putc('\302'); putc('\260'); // doesn't work with CP_UTF8

puts("\302\260"); // correctly writes UTF-8 data to Windows console with CP_UTF8 

ほとんどのオペレーティングシステムとは異なり、Windowsのコンソールは、バイトのストリームを受け入れる単なる別のファイルではありません。これは、プログラムによって作成および所有され、独自のWIN32APIを介してアクセスされる特別なデバイスです。問題は、コンソールが書き込まれるときに、APIがそのAPIの使用で渡されたデータの範囲を正確に認識し、データが不完全である可能性があることを考慮せずに、狭い文字から広い文字への変換が行われることです。 コンソールAPIへの複数の呼び出しを使用してマルチバイト文字が渡される場合、個別に渡される各部分は不正なエンコーディングと見なされ、そのように扱われます。

これを回避するのは簡単なはずですが、MicrosoftのCRTチームはそれを問題ではないと考えていますが、コンソールで作業するチームはおそらく気にしません。

Wchar_tへの変換を正しく処理する独自のstreambufサブクラスを実装することで解決できます。つまりマルチバイト文字のバイトが別々に来る可能性があるという事実を考慮して、書き込み間の変換状態を維持します(例:std::mbstate_t)。

16
bames53

SetConsoleOutputCPの代わりに、stdout_ setmode を使用する別のトリックがあります。

_// Includes needed for _setmode()
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);  
    wchar_t * unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", unicode_text);
    return 0;
}
_

SetConsoleOutputCP(CP_UTF8);の呼び出しを削除することを忘れないでください

13
huysentruitw
//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>
int main() {
  SetConsoleOutputCP(65001);
  const char unicode_text[]="aäbcdefghijklmnoöpqrsßtuüvwxyz";
  printf("%s\n", unicode_text);
}

結果:
aäbcdefghijklmnoöpqrsßtuüvwxyz

6
vladasimovic

私も同様の問題を抱えていましたが、既存の答えはどれもうまくいきませんでした。私が観察した他の何かは、UTF-8文字をplain文字列リテラルに貼り付けると、正しく印刷されるが、UTF-8リテラルを使用しようとすると(u8"text")、文字はコンパイラによって屠殺されます(一度に1バイトずつ数値を出力することによって証明されます;rawLinuxマシンで検証されたように、リテラルには正しいUTF-8バイトがありましたが、UTF-8リテラルはガベージでした)。

少し突っ込んだ後、私は解決策を見つけました: /utf-8 。それで、すべてがうまくいきます。私のソースはUTF-8であり、明示的なUTF-8リテラルを使用でき、出力は他の変更を必要とせずに機能します。

2
Matthew

コンソールはUTF-8文字を表示するように設定できます:@vladasimovic Answers SetConsoleOutputCP(CP_UTF8)を使用できます。または、DOSコマンド_chcp 65001_またはメインプログラムのシステムコールsystem("chcp 65001 > nul")によってコンソールを準備することもできます。ソースコードもUTF-8に保存することを忘れないでください。

UTF-8サポートを確認するには、

_#include <stdio.h>
#include <windows.h>

BOOL CALLBACK showCPs(LPTSTR cp) {
  puts(cp);
  return true;
}

int main() {
  EnumSystemCodePages(showCPs,CP_SUPPORTED);
}
_

_65001_がリストに表示されます。

Windowsコンソールはデフォルトで OEMコードページ を使用し、ほとんどのデフォルトのラスターフォントは国別文字のみをサポートします。 Windows XP以降はTrueTypeフォントもサポートしており、不足している文字を表示する必要があります(@Devenecは彼の回答でLucida Consoleを提案しています)。

Printfが失敗する理由

@ bames53が彼の答えで指摘しているように、Windowsコンソールはストリームデバイスではないため、マルチバイト文字のすべてのバイトを書き込む必要があります。時々printfはジョブを台無しにし、バイトを1つずつ出力バッファに入れます。結果をsprintfしてからputsを使用するか、蓄積された出力バッファのみを強制的にフラッシュしてみてください。

すべてが失敗した場合

TF-8形式 に注意してください:1文字は1-5バイトとして表示されます。この関数を使用して、文字列内の次の文字にシフトします。

_const char* ucshift(const char* str, int len=1) {
  for(int i=0; i<len; ++i) {
    if(*str==0) return str;
    if(*str<0) {
      unsigned char c = *str;
      while((c<<=1)&128) ++str;
    }
    ++str;
  }
  return str;
}
_

...そしてバイトをUnicode番号に変換するこの関数:

_int ucchar(const char* str) {
  if(!(*str&128)) return *str;
  unsigned char c = *str, bytes = 0;
  while((c<<=1)&128) ++bytes;
  int result = 0;
  for(int i=bytes; i>0; --i) result|= (*(str+i)&127)<<(6*(bytes-i));
  int mask = 1;
  for(int i=bytes; i<6; ++i) mask<<= 1, mask|= 1;
  result|= (*str&mask)<<(6*bytes);
  return result;
}
_

次に、MultiByteToWideCharのようなワイルド/古代/非標準のwinAPI関数を使用してみることができます(前にsetlocale()を呼び出すことを忘れないでください!)

または、Unicodeテーブルからアクティブな作業コードページへの独自のマッピングを使用できます。例:

_int main() {
  system("chcp 65001 > nul");
  char str[] = "příšerně"; // file saved in UTF-8
  for(const char* p=str; *p!=0; p=ucshift(p)) {
    int c = ucchar(p);
    if(c<128) printf("%c\n",c);
    else printf("%d\n",c);
  }
}
_

これは印刷する必要があります

_p
345
237
353
e
r
n
283
_

コードページがそのチェコ語の句読点をサポートしていない場合は、345 => r、237 => i、353 => s、283 => eをマップできます。チェコ語専用の文字セットは少なくとも5つ(!)あります。異なるWindowsロケールで読み取り可能な文字を表示するのは恐ろしいことです。

2
Jan Turoň

私は次の方法で問題を解決しました:

Lucida Consoleはumlautsをサポートしていないようです。そのため、たとえば、コンソールフォントをConsolasに変更すると機能します。

#include <stdio.h>
#include <Windows.h>

int main()
{
    SetConsoleOutputCP(CP_UTF8);

    // I'm using Visual Studio, so encoding the source file in UTF-8 won't work
    const char* message = "a" "\xC3\xA4" "bcdefghijklmno" "\xC3\xB6" "pqrs" "\xC3\x9F" "tu" "\xC3\xBC" "vwxyz";

    // Note the capital S in the first argument, when used with wprintf it
    // specifies a single-byte or multi-byte character string (at least on
    // Visual C, not sure about the C library MinGW is using)
    wprintf(L"%S", message);
}

編集:愚かなタイプミスと文字列リテラルのデコードを修正しました。申し訳ありません。

1
Devenec

UTF-8はWindowsコンソールでは機能しません。限目。私はすべての組み合わせを試しましたが、成功しませんでした。 ANSI/OEM文字の割り当てが異なるために問題が発生するため、問題はないと言う回答もありますが、そのような回答は、7ビットのプレーンASCIIまたは同一のANSI/OEMコードページ(中国語、日本語)。

UTF-16とワイド文字関数を使用することに固執するか(ただし、OEMコードページの256文字に制限されています-中国語/日本語を除く)、またはOEMコードページを使用しますASCIIソースファイル内の文字列。

はい、まったく混乱しています。

多言語プログラムの場合、文字列リソースを使用し、中間バッファーなしでLoadStringOem()を使用してUTF-16リソースをOEM文字列に自動変換するWideCharToMultiByte()関数を作成しました。 Windowsはリソースから適切な言語を自動選択するため、ターゲットのOEMコードページに変換可能な言語で文字列をロードすることが期待されます。

結果として、英語と米国の言語リソースに8ビットの活版印刷文字を使用しないでください(Ellipsis…と引用符「」)。言語の一致が検出されない場合(フォールバックなど)、WindowsによってEnglish-USが選択されます。例として、ドイツ語、チェコ語、ロシア語、英語-米国のリソースがあり、ユーザーが中国語を使用している場合、テキストを見栄えよくすると、きれいに作成された活版印刷の代わりに英語とゴミが表示されます。

現在、Windows 7および10では、SetConsoleOutputCP(65001/*aka CP_UTF8*/)は期待どおりに機能します。ソースファイルはBOMなしでUTF-8に保持する必要があります。そうしないと、文字列リテラルがコンパイラによってANSIに再コード化されます。さらに、コンソールフォント必須には必要な文字が含まれ、必須は「ターミナル」である必要があります。残念ながら、両方の言語パックをインストールしても、ウムラウトと漢字の両方をカバーするフォントがないため、すべての文字の形状を一度に表示することはできません。

0
Henrik Haftmann