none
標準的な出力ストリーム(wofstream ?) を使ってUTF-16 LE (BOM付き) ファイルに出力したい。 RRS feed

  • 質問

  • 標準的な出力ストリーム(wofstream ?) を使ってUTF-16 LE (BOM付き) ファイルに出力したい。コンソールアプリを次のように作成してみました。実行結果では、UTF-8 ファイルが作成されて、ASCII 文字以外を出力しようとすると、そこで出力が中断されました。どのようにすればよいでしょうか? よろしくお願いします。

    #include <iostream>
    #include <locale.h>      // _wsetlocale のため。この関数は、C ランタイムライブラリ(CRT) の関数。
    #include <fstream>
    #include <iomanip>
    using namespace std;
    int s13()
    {
     wofstream fout(L"test13b-8.txt");
     if (!fout) {
      wcout << L"ファイルをオープンできませんでした。\n";
      return 1;
     }
     wofstream fou2(L"test13b-6.txt");
     if (!fou2) {
      wcout << L"ファイルをオープンできませんでした。\n";
      return 1;
     }
     const int num = 5;
     int test[num]{ 75, 80, 65, 70, 90};
     
     for (int j = 0; j < num; j++) {
      fout << L"No." << j + 1 << setw(5) << test[j] << L"\n";
      fou2 << L"No." << j + 1 << L"   "  << test[j] << L"\n";
      if (j == 2) fou2 << L"このファイルを UTF-16 bLE にするために、\n"
       << L"ASCII以外の文字(日本語文字)を出力してみます。\n";
     }
     fou2.close();
     fout.close();
     return 0;
    }

    int main()
    {
     _wsetlocale(LC_ALL, L"jpn");  //wcout, wcin でUnicode を扱えるようにする。
     wchar_t a[64]{ L"本プログラムは ca7 です。(wcoutで出力した)\n" };
     wcout << a;
      s13();
     wcout << L" \n";
     return 0;
    }


    2020年5月21日 4:17

回答

  • iostreamとlocaleとcodecvtと、まあC++三重苦ですな。
    文字コードの変換は自分でやって、ファイルへの書き込みは、いにしえのbinary-mode-FILEとかで書いた方がスッキリすると思いますケドね。

    1.codecnv_utf16なロケールをimbueする
    fout.imbue(std::locale(std::locale(), new std::codecvt_utf16<wchar_t, 0xffff, (std::codecvt_mode)(std::codecvt_mode::generate_header | std::codecvt_mode::little_endian)>()));
    text-modeだとstd::endlが0d-00-0a-00でなく、0d-0a-00で出てくるような問題があります

    2.そもそもVisualC++のwchar_tはutf16なんだから変換はいらない。何も気にせずバイナリモードでかけばいい。BOMが必要?自分で足せばOK。
    どうしてもiostreamのやり方がよいのであれあば、wstringstreamに<<していって、最後にstr()取ってファイルに書く。
    (もちろん、ちゃんとfopenのCCSを指定してあげる手もあります。)

    _wfopen_s(&fp, L"test2.bin", L"wb"); fputwc(L'\xfeff', fp);
    std::wstring ws = wstringstr.str(); fwrite(ws.c_str(), 2, ws.length(), fp); fclose(fp);


    jzkey

    • 回答としてマーク mayumee 2020年5月23日 1:34
    2020年5月21日 10:52
  • mayumeeさん、こんにちは。フォーラムオペレーターのクモです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    jzkeyさまに賛同します。
    しかし、<codecvt>全体はC++17では非推奨であり、置き換えるのに適したライブラリはありません。

    なぜBOMを追加する必要があるのですか?ファイルのエンコーディングがutf-16leであると宣言されている場合、BOMはありません。

    MultiByteToWideCharを使用してみることをお勧めします。

    どうぞよろしくお願いいたします。

    MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク mayumee 2020年5月23日 1:35
    2020年5月22日 5:46
    モデレータ

すべての返信

  • iostreamとlocaleとcodecvtと、まあC++三重苦ですな。
    文字コードの変換は自分でやって、ファイルへの書き込みは、いにしえのbinary-mode-FILEとかで書いた方がスッキリすると思いますケドね。

    1.codecnv_utf16なロケールをimbueする
    fout.imbue(std::locale(std::locale(), new std::codecvt_utf16<wchar_t, 0xffff, (std::codecvt_mode)(std::codecvt_mode::generate_header | std::codecvt_mode::little_endian)>()));
    text-modeだとstd::endlが0d-00-0a-00でなく、0d-0a-00で出てくるような問題があります

    2.そもそもVisualC++のwchar_tはutf16なんだから変換はいらない。何も気にせずバイナリモードでかけばいい。BOMが必要?自分で足せばOK。
    どうしてもiostreamのやり方がよいのであれあば、wstringstreamに<<していって、最後にstr()取ってファイルに書く。
    (もちろん、ちゃんとfopenのCCSを指定してあげる手もあります。)

    _wfopen_s(&fp, L"test2.bin", L"wb"); fputwc(L'\xfeff', fp);
    std::wstring ws = wstringstr.str(); fwrite(ws.c_str(), 2, ws.length(), fp); fclose(fp);


    jzkey

    • 回答としてマーク mayumee 2020年5月23日 1:34
    2020年5月21日 10:52
  • "\x0D\x0A\x00" バグがどうしようもありませんが、<codecvt>全体がC++17で非推奨なのも辛いですね…。
    2020年5月21日 12:06
  • mayumeeさん、こんにちは。フォーラムオペレーターのクモです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    jzkeyさまに賛同します。
    しかし、<codecvt>全体はC++17では非推奨であり、置き換えるのに適したライブラリはありません。

    なぜBOMを追加する必要があるのですか?ファイルのエンコーディングがutf-16leであると宣言されている場合、BOMはありません。

    MultiByteToWideCharを使用してみることをお勧めします。

    どうぞよろしくお願いいたします。

    MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク mayumee 2020年5月23日 1:35
    2020年5月22日 5:46
    モデレータ
  • jzkeyさん、返信ありがとうございます。

    私は、最近C++を学び始めた初心者です。1.imbueするのやり方はすぐには理解できそうにありません。すみません。質問の際に、初心者であることを書いておいた方がよかったと思いました。

    2.以降については、C言語のプログラミング経験が少しありますので、説明の中にあるクラスや関数を調べてゆけば、なんとかできそうな気がします。wstringstreamを使う場合、操作子が使えるようですと、使いやすいと思います。このあたりのことをふまえてどのようにするのかを考えたいと思います。

    2020年5月22日 7:43
  • 佐祐理さん、返信ありがとうございます。

    この内容は、jzkeyさんの1.imbueすることについての指摘と思われますが、いかがでしょうか。初心者ですので<codecvt>について、すぐには分かりませんが、codecvtという単語がjzkeyさんの説明に出てきていますので。そうだとすると、このやり方は、非推奨ということになりそうですので、使わないようにしたいと思います。

    2020年5月22日 7:45
  • > UTF-16 LE (BOM付き) ファイルに出力したい

    なぜBOMを追加する必要があるのですか?ファイルのエンコーディングがutf-16leであると宣言されている場合、BOMはありません。


    1. UTF-16 LE (BOM付き)
    2. UTF-16LE
    3. UTF-16 (BOM付き、リトルエンディアン)

    mayumee さんが書かれていたのは 1 の表現ですが、実際に必要だったのは 3 の "UTF-16" だったりはしないでしょうか?

    "UTF-16" であればビッグエンディアンだけでなくリトルエンディアンもありえますし、BOM 無しも BOM 付きも両方ありえます。先頭 2 バイトが FF,FE なら、UTF-16 のリトルエンディアン(BOM 付き) の意味になります。

    一方 "UTF-16LE" の場合、先頭 2 バイトに U+FEFF を付けてはいけない ことになっています。実際の処理系の実装がどうなっているかはさておき、"UTF-16LE" の先頭に FF,FE の 2 バイトが含まれていた場合、それは BOM ではなく ZWNBSP の 1 文字 (U+FEFF) として扱われる仕様らしいです(そういう処理系は見たことが無いですが)。

    そして kumo-msft さんの投稿では、2 の "UTF-16LE" の意味で解釈されたと思います。"UTF-16LE" 上で、そこに BOM が含まれるのは Unicode 仕様として誤りなので、BOM 無しで記述すべき…という提案とお見受けします。

    ※"UTF-16LE" として出力されたファイルは、BOM 無しのリトルエンディアンな "UTF-16" ファイルと完全に同一です。

    2020年5月22日 8:28
  • クモさん、質問への返信ありがとうございます。  mayumee です。

    なぜBOMを追加する必要があるのですかへのお答えです。それは、私がふだんからメモ帳で作成しているテキストファイルは、文字コードをUTF-16 LE(以前はUnicodeといっていました)としているためです。このテキストファイルを入出力できるようにコンソールアプリを作成したいからです。このテキストファイルにはBOMが付いています。


    2020年5月22日 12:24
  • 私がふだんからメモ帳で作成しているテキストファイルは、文字コードをUTF-16 LE(以前はUnicodeといっていました)としているためです。

    その目的であれば、メモ帳に文字コードを <自動検出> させるためにも、BOM 付きのファイルとして生成することが望ましいでしょうね。

    jzkey さんも書かれているように、バイナリファイルとして生成していくのが確実そうです。

    そしてメモ帳でいうところの "UTF-16 LE" とは、"UTF-16LE" のことではないはず。※空白の有無に注意

    "UTF-16LE" の場合は BOM が禁止されているのですが、
    "UTF-16" (リトルエンディアン) に対する BOM は問題ないですね。

    2020年5月22日 13:05
  • 魔界の仮面弁士さん、UTF-16の表記の説明ありがとうございます。  mayumeeです。

    私が魔界の仮面弁士さんへの返信を用意している間に、魔界の仮面弁士さんの二つ目の投稿がありました。ありがとうございます。魔界の仮面弁士さんは、理解していただいていると思います。くどくなると思いますが、用意していました返信を、修正しないで、そのまま以下にのせます。

    ---- 

    そういえば、UTF-16の正式な表記は、ウィキペディアにもあったような気がします。今回、私が使った表記は、(クモさんへのお答えに書きましたように)メモ帳で文字コードを指定するUTF-16 LEです。メモ帳を「名前を付けて保存」しようとするときに、保存ボタンの左に出てきます。このファイルはBOM付きですので、それを強調するために(BOM付き)と補足しました。たしかに、この表記ですと、まぎらわしいかと思います。このメモ帳のファイルは、魔界の仮面弁士さんの説明のUTF-16 (BOM付き リトルエンディアン) になると思います。

    2020年5月23日 1:32