|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
MSYS2/mingw-w64での日本語アプリケーション開発を考察する。
日本語アプリケーションの開発は難しい。C++規格の国際化対応は完全に程遠く、その実装はエラー含みを警戒した方が良い。さらにウィンドウズのユニコードはwchar_tサイズに代表されるように不完全で、UTF-8とUTF-32はほとんど無視されてきた。我々は英語の読み書きに酷く苦労するが、日本語アプリケーションの開発はそれ以上の苦渋となる場合がある。
コンソールアプリケーションは互換性を重視し、できるだけC++標準ライブラリの範疇でプログラミングしたい。ソースコードはシフトJISあるいはUTF-8で記述する。統合開発環境Code::Blocksでは[Settings|Editor|General settings|Encoding settings|Encoding]でデフォルトのエディタ文字コードを設定する。ファイル個別には[Edit|File encoding]で設定する。ソースコードをシフトJISで記述した場合はコンパイラmingw-w64の入力文字コードをデフォルト(UTF-8)から変更してコンパイル(-finput-charset=CP923)する。
シフトJIS文字列あるいはUTF-8文字列の入出力を検討する。実行文字コードをデフォルト(UTF-8)のまま以下をビルド実行する。
実行文字コードがUTF-8でコマンドプロンプトのデフォルトコードページがシステムロケールコードページである932(シフトJIS)なので、2行目(日本語文字列)出力は化ける。実行文字コードをシフトJISに変更してコンパイル(-fexec-charset=CP932)するか、コマンドプロンプトのコードページをUTF-8に変更(CHCP 65001)すれば解決する。MSYS2のPOSIX互換ターミナルはデフォルト文字コードがUTF-8なので結果は反対となる。POSIX互換ターミナルでシフトJISを表示させるには右クリック[Options]で開く[Options]ダイアログ[Text]ページの[...|Character set]でSJISを選択し[Apply]を押す。
入力も実行文字コードと表示を合わせれば問題ないはずだが、コマンドプロンプトではUTF-8の入力が上手く行かない。
UTF-16文字列の入出力を検討する。ワイド実行文字コードをデフォルト(UTF-16)のまま以下をビルド実行する。実行文字コードはUTF-8でもシフトJISでも関係ない。
2行目(日本語文字列)、3行目(2符号長UTF-16文字列)出力に失敗する。16ビット符号文字列から8ビット文字符号文字列への文字コード変換がデフォルトのまま("C"ロケール)でASCII文字以外を変換できないためである。
これらをシフトJISを表示するコマンドプロンプトあるいはPOSIX互換ターミナルに出力するには以下に修正して実行する。ただし出力3行目はシフトJISに変換できず表示できない。
これでUTF-16文字列をコマンドプロンプトあるいはPOSIX互換ターミナルから入力する事もできる。ただしシフトJISで表示できない文字は入力できない。
UTF-8を表示するコマンドプロンプトあるいはPOSIX互換ターミナルに出力するには以下に修正して実行する。ただしコマンドプロンプトは出力3行目を正しく表示できない。
これでUTF-16文字列をPOSIX互換ターミナルから入力する事もできる。ただしコマンドプロンプトからは正常に入力できず、先の2符号長UTF-16文字(基本多言語面以外の文字)を表示できないことを含め、そのUTF-8機能は非常に限定されているものと思われる。
これらのサンプルソースコードは以下の対策を行っている。なお文字コード変換の定義については、標準ライブラリのファイルストリームでより詳細な議論を行っている。
C++標準入出力(cout、cin、wcout、wcinなど)はC言語標準入出力(printf、scanf、wprintf、wscanfなど)との同期(JTC1/SC22/WG21 N4659 30.4.2/p5)をデフォルトとするが、ios_base::sync_with_stdio関数でこれを変更できる(N4659 30.5.3.4)。そのmingw-w64実装はC++標準入出力の入出力バッファ(ストリームバッファ)切り替えで、同期用をstdio_sync_filebuf(GCC 14.1.0 Standard C++ Library Manual B.6.5 API Evolution and Deprecation at ver. 3.4)、非同期用をstdio_filebuf(Library Manual 27.1 Derived filebufs)とする。両方とも実装依存の拡張だが、stdio_filebufがbasic_filebuf(N4659 30.9.2)の派生クラステンプレートである一方、stdio_sync_filebufは名前に反し継承していない。文字コード変換ファセット(codecvtファセット)はbasic_filebufが利用するため(N4659 30.9.2/p5)、C++標準入出力の文字コード変換は非同期でのみ行える。これはC++標準入出力とC言語標準入出力がcharベースで混在するための妥協で(Library Manual 13.5.2 Performance)、1符号長より長い文字が混在すると複雑な事態を招くためと考えられる(サイト作成者の見解)。
次のサンプルコードでこれらを確認する事ができる。
C++標準入出力の文字コード変換を有効にするにはios_base::sync_with_stdio(false)でC言語標準入出力との同期を解除する。これは全ての標準入出力を行う前に行うべきで、その場合C言語標準入出力は使わない。
mingw-w64実装におけるC言語標準入出力を非同期としたC++標準入出力を含み、ファイル入出力(ファイルストリーム)はそれぞれに設定(imbue)されているロケールのcodecvtファセットで文字コードを変換する。wchar_tが16ビットの場合の規格の定義する主なcodecvtファセットを示す(N4659 25.4.1.4/p3、D.15.1)。
ヘッダ | codecvtファセット | 内部型 | 外部型 | 規格 | mingw-w64実装 | 標準入出力 | 備考 |
---|---|---|---|---|---|---|---|
<locale> | codecvt<char,char,mbstate_t> | char | char | 無変換 | 無変換 | cout、cin | デフォルト |
codecvt<wchar_t,char,mbstate_t> | wchar_t | char | 実装依存 | setlocaleに従う | wcout、wcin | デフォルト | |
codecvt<char16_t,char,mbstate_t> | char16_t | char | UTF-16⇔UTF-8 | UTF-16⇔UTF-8 | - | ||
<codecvt> | codecvt_utf8<wchar_t> | wchar_t | char | UCS-2⇔UTF-8 | UTF-16⇔UTF-8 | wcout、wcin | C++17非推奨 |
codecvt_utf8_utf16<wchar_t> | wchar_t | char | UTF-16⇔UTF-8 | UTF-16⇔UTF-8 | wcout、wcin | C++17非推奨 |
UTF-16とシフトJISの相互変換にはcodecvt<wchar_t,char,mbstate_t>を用いるが、これはロケールが標準で所有するワイド文字列codecvtファセットである。このファセットの16ビット符号文字列はUTF-16に固定され、8ビット符号文字列はC言語標準ライブラリのsetlocale関数がグローバル設定するロケールに従う。これを設定しないと"C"ロケールが選択され8ビット符号文字列はASCIIとなりそれ以外を変換できない。設定できる日本語の文字コードはシフトJISとEUC-JPに限定され例えばUTF-8は設定できない。ロケール名に空文字列""を与えるとシステムロケールが選択され日本語環境の文字コードはシフトJISである。本来これはC++標準ライブラリのglobal::localeメンバ関数が設定するべきだがmingw-w64(正確にはGCCウィンドウズ実装の全て)の実装が不十分でC言語標準ライブラリ関数を使わざるを得ず、知らなければmingw-w64実装のワイド文字列標準codecvtはASCII以外を変換できないと容易に誤解する。
UTF-16とUTF-8の相互変換にはcodecvt_utf8_utf16<wchar_t>を用いれば良く、ロケールの標準codecvt<wchar_t,char,mbstate_t>に置き換える。このcodecvtファセットはmingw-w64実装で規格通りに動作する。ただしこれはC++17で非推奨となった(N4659 D.15.1)。
C++23でも非推奨のままに残るが(N4950 D.26.1)C++26で廃止される予定にある。C++17ではcodecvt<char16_t,char,mbstate_t>がその代替とするが、これはwcout、wcin(より一般には内部型wchar_t外部型charのファイルストリームバッファを用いるストリーム)に使えない。C++20はchar8_tの追加でcodecvt<char16_t,char8_t,mbstate_t>が代替となる。
UTF-16文字列を8ビット符号文字列に変換する別解としてcodecvtファセットを自分で作る。シフトJISあるいはUTF-8への変換はC++標準ライブラリで可能である事は説明したが、その他の文字コードへは変換できない。加えてシフトJISに変換するcodecvt<wchar_t,char,mbstate_t>ファセットの処理速度はベンチマークによれば非常に遅い。ファイル入出力への利用拡大を念頭に文字コード変換ライブラリを利用して任意文字コードへ変換するカスタムcodecvtを開発する。
UTF-16をシフトJISを表示するコマンドプロンプトあるいはPOSIX互換ターミナルへ標準入出力する。ファセットは自作TMyCodeCvtクラステンプレートの特殊化で、ソースコードはTMyCodeCvt.hとして供給されている。ワイド実行文字コードはデフォルト(UTF-16)のままでコンパイルされているとする。
これでUTF-16文字列を正常に入出力できるが文字セットはシフトJISで扱えるものに限定される。NMyEncoding::ShiftJISをNMyEncoding::UTF8に書き換えればUTF-8を表示するコマンドプロンプトあるいはPOSIX互換ターミナルへ標準入出力するが、コマンドプロンプトは2符号長UTF-16文字を表示できず入力も正常に行えない。
TMyCodeCvtは可能な限り汎用性に配慮した。参考として、以下とすればUTF-8文字列をシフトJISを表示するウィンドウズコマンドプロンプで入出力できる。実行文字コードはデフォルト(UTF-8)のままでコンパイルされているものとする。
本サイトはウィンドウズデスクトップアプリケーション開発にwxWidgetsライブラリを用いる。統合開発環境としてCode::Blocksを選択し、Code::Blocksに付属するwxSmithのRAD(Rapid Application Development)を利用する。wxWidgetsライブラリはマルチプラットフォームターゲットだが、本サイトはあくまでウィンドウズをターゲットとし必要に応じウィンドウズAPIを直接利用する。これらを理由としてコンソールアプリケーションと異なり互換性に重きを置かない。
wxWidgetsライブラリ利用アプリケーションテスト用として、統合開発環境カスタマイズ(1)で導入したウィザードで動作確認用のウィンドウズデスクトップアプリケーションをプロジェクト名WindowsDesktopTestで作成する。WindowsDesktopTestFrame.cppソースコード内のWindowsDesktopTestFrame::OnAboutメンバ関数を書き換えて様々なテストを行う。例えば以下でビルドしてアプリケーション実行すれば、[Help|About]で表示されるダイアログは期待通りHello world!を表示する。
8ビット符号文字列をテストするが、結果は物理ファイルを記述する文字コードおよびコンパイラオプション(入力文字コード、実行文字コード)に依存する。ソースコード変更を伴わないコンパイラオプション変更を実行ファイルへ反映するにはそれぞれにおいて[Build|Rebuild]する。
物理ファイル | 入力文字コード | 実行文字コード | コンパイル | 表示 |
---|---|---|---|---|
UTF-8 | UTF-8 | UTF-8 | 成功 | (空文字列) |
CP932 | 成功 | こんにちは世界! | ||
CP932 | UTF-8 | 失敗(failure to convert CP932 to UTF-8) | - | |
CP932 | 失敗(failure to convert CP932 to UTF-8) | - | ||
CP932 | UTF-8 | UTF-8 | 成功 | こんにちは世界! |
CP932 | 失敗(conversion to execution character set: Illegal byte sequence) | - | ||
CP932 | UTF-8 | 成功 | (空文字列) | |
CP932 | 成功 | こんにちは世界! |
一つの例外を除き物理ファイル文字コードと入力文字コードが一致しないとコンパイルは失敗する。例外を除き、実行文字コードがCP932でないと期待される文字列は表示されない。wxWidgetsライブラリは独自の文字列型wxStringを持ち、wxMessageBoxを含むほとんどの関数が文字列引数としてwxStringを受け取る。wxStringはconst char*からの変換コンストラクタを持つが、変換される文字列は現在のシステムロケールに従いCP932とするため、これにUTF-8文字列を渡すと正しく構築されない。
興味深い例外は物理ファイルCP932で入力文字コードと実行文字コードが共にUTF-8のケースで、コンパイルに成功し表示も正常となる。入力文字コードと実行文字コードが同じためコンパイラは何の変換も加えず、結果としてCP932文字列がそのままwxStringコンストラクタへ渡されるためと考えられる。これはコンパイラの意図しない動作で避けるべきであろう。以降においてこの例外は考慮から外し、物理ファイル文字コードと入力文字コードは常に一致するとして"入力文字コード"で両者を総称する。
実行文字コードUTF-8としてUTF-8文字列を渡す場合はwxStringの変換コンストラクタに頼れず、明示的な変換をする。
入力文字コードUTF-8とすればシフトJIS外の文字も直接使用できる。
16ビット符号文字列をテストする。
ワイド実行文字コードがデフォルト(UTF-16)であればコンパイルは成功し表示も正常となる。wxStringはconst wchar_t*からの変換コンストラクタを持ち、変換される文字列はウィンドウズでUTF-16となる。入力文字コードをUTF-8とすればシフトJIS外の文字も直接使用できる。
wxWidgetsライブラリを利用するソースコードの文字列は_マクロで囲まれることが多いが、その実体はwxString型を引数とするwxGetTranslation関数で上記の検討結果は変わらない。これらはwxWidgetsライブラリの用意する国際化機能の一部を構成するものであり、_マクロはwx\translation.hに定義されている。
wxSmithで入力される文字列もこれに従い_マクロで囲んでソースコードへ挿入される。メインウィンドウのwxSmithリソースファイル(wxsmith\WindowsDesktopTestFrame.wxs)をCode::Blocksエディタで開くとwxSmithエディタに読み込まれる。wxSmithエディタ上部ツールホルダーパネルにあるメニューバー(wxMenuBar)アイコンをダブルクリックして[MenuBar editor]ダイアログを開く。[Content]ツリービューのHelpというラベルを持つノードを選択し、[Options|New]を押してNew Menuというラベルを持つ新たなメニューアイテムを作成し、[Options|Label]でそのラベルを日本語メニューに変更して[OK]を押す。WindowsDesktopTestFrame.cppソースコードのメインウィンドウコンストラクタにメニューアイテムが追加されていることを確認する。
実行文字コードCP932なら[日本語メニュー]が正常にメニューバーに表示される。実行文字コードUTF-8では文字化けするが、以下いずれかの修正で正常に表示される。
これらの修正はしかし、wxSmithエディタで編集を行うたびにリセットされてしまう。別解として上記修正は行わず、_マクロを以下いずれかに修正定義してしまうことも考えられる。国際化機能を使わないと決めてしまえば、それぞれのwxGetTranslation関数コールは省略できる。
wxSmithの文字コードはUTF-8でシフトJIS外の文字も入力できる。入力文字コードをUTF-8とすれば、[日本語メニュー]ラベルをwxSmithで😀😁😂😃に変更しても正常にソースコードに挿入され、_マクロ修正定義で正常表示される。
_マクロ修正定義はしかし、ライブラリの標準的な使用方法を大きく逸脱し推奨できない。
特にウィンドウズデスクトップアプリケーションにおける開発を検討する。
日本語文字列は全て16ビット符号文字列(UTF-16)として、シフトJIS外文字をソースコード内に書き込む必要があるなら入力文字コードをUTF-8とすれば良い。しかしながらwxSmithはユニコード文字列(ただしUTF-8)を入力できるのに、その文字列はソースコードにおいてシステムロケールの決定する文字コード(日本語ウィンドウズはCP932)として扱われる。これはwxSmithの大きな設計ミスと思われ、wxSmithを利用して日本語アプリケーションを開発するにはこの課題をクリアする必要がある。三つの方法を提案する。
最もシンプルな解決法だが以下のデメリットを持つ。
シフトJIS外文字もwxSmithから入力でき、システムロケールと無関係に正しい表示が行える。ただし以下のデメリットを持つ。
アプリケーション開発時に文字コードの問題から完全に自由となる。様々な開発ツールを安心して利用できるし、Code::Blocksエディタ設定とコンパイルオプションの文字コード不整合さえ問題とならない。さらに多言語対応アプリケーションへ容易に拡張できる。しかし言うまでもなく大きなデメリットがある。
サイト作成者は、しかし結局これが最良の解ではないかという不幸な結論に傾きつつある。