パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
日本語アプリケーションをどのように開発するか

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)する。

8ビット符号文字列

シフトJIS文字列あるいはUTF-8文字列の入出力を検討する。実行文字コードをデフォルト(UTF-8)のまま以下をビルド実行する。

#include <iostream>
int main()
{
std::cout<<"Hello world!"<<std::endl;
std::cout<<"こんにちは世界!"<<std::endl;
return 0;
}

実行文字コードが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]を押す。

覚え書き
[Options]ダイアログで[Apply]でなく[Save]を押すと変更された文字コード設定が/home/user/.minttyrcに保存される。この設定ファイルはMSYS2の全POSIXサブシステムに共通で、次回POSIX互換ターミナル起動時にシェルはこれに従い環境変数LANGを設定する。

入力も実行文字コードと表示を合わせれば問題ないはずだが、コマンドプロンプトではUTF-8の入力が上手く行かない。

16ビット符号文字列

UTF-16文字列の入出力を検討する。ワイド実行文字コードをデフォルト(UTF-16)のまま以下をビルド実行する。実行文字コードはUTF-8でもシフトJISでも関係ない。

#include <iostream>
int main()
{
std::wcout<<L"Hello world!"<<std::endl;
std::wcout<<L"こんにちは世界!"<<std::endl;
std::wcout<<L"\U0001F600\U0001F601\U0001F602\U0001F603"<<std::endl;
return 0;
}

2行目(日本語文字列)、3行目(2符号長UTF-16文字列)出力に失敗する。16ビット符号文字列から8ビット文字符号文字列への文字コード変換がデフォルトのまま("C"ロケール)でASCII文字以外を変換できないためである。

これらをシフトJISを表示するコマンドプロンプトあるいはPOSIX互換ターミナルに出力するには以下に修正して実行する。ただし出力3行目はシフトJISに変換できず表示できない。

#include <iostream>
int main()
{
std::ios_base::sync_with_stdio(false);
//std::locale::global(std::locale{""}); // Doesn't work.
std::setlocale(LC_CTYPE,"");
std::wcout<<L"Hello world!"<<std::endl;
std::wcout<<L"こんにちは世界!"<<std::endl;
std::wcout<<L"\U0001F600\U0001F601\U0001F602\U0001F603"<<std::endl;
return 0;
}

これでUTF-16文字列をコマンドプロンプトあるいはPOSIX互換ターミナルから入力する事もできる。ただしシフトJISで表示できない文字は入力できない。

#include <iostream>
int main()
{
std::ios_base::sync_with_stdio(false);
//std::locale::global(std::locale{""}); // Doesn't work.
std::setlocale(LC_CTYPE,"");
auto ans=std::wstring{};
std::wcout<<L"何か入力してください。"<<std::endl;
std::wcin>>ans;
std::wcout<<ans<<L"が入力されました。"<<std::endl;
return 0;
}

UTF-8を表示するコマンドプロンプトあるいはPOSIX互換ターミナルに出力するには以下に修正して実行する。ただしコマンドプロンプトは出力3行目を正しく表示できない。

#include <iostream>
#include <codecvt>
int main()
{
std::ios_base::sync_with_stdio(false);
std::wcout.imbue(std::locale{std::locale{},new std::codecvt_utf8_utf16<wchar_t>{}});
std::wcout<<L"Hello world!"<<std::endl;
std::wcout<<L"こんにちは世界!"<<std::endl;
std::wcout<<L"\U0001F600\U0001F601\U0001F602\U0001F603"<<std::endl;
return 0;
}

これでUTF-16文字列をPOSIX互換ターミナルから入力する事もできる。ただしコマンドプロンプトからは正常に入力できず、先の2符号長UTF-16文字(基本多言語面以外の文字)を表示できないことを含め、そのUTF-8機能は非常に限定されているものと思われる。

#include <iostream>
#include <codecvt>
int main()
{
std::ios_base::sync_with_stdio(false);
auto loc=std::locale{std::locale{},new std::codecvt_utf8_utf16<wchar_t>{}};
std::wcout.imbue(loc);
std::wcin.imbue(loc);
auto ans=std::wstring{};
std::wcout<<L"何か入力してください。"<<std::endl;
std::wcin>>ans;
std::wcout<<ans<<L"が入力されました。"<<std::endl;
return 0;
}

これらのサンプルソースコードは以下の対策を行っている。なお文字コード変換の定義については、標準ライブラリのファイルストリームでより詳細な議論を行っている。

  • C言語標準入出力の非同期化
  • 文字コード変換の定義
覚え書き
入出力ライブラリiostreamは難解で困惑する。locale::facetクラス(JTC1/SC22/WG21 N4659 25.3.1.1.2)はcollate、ctype、codecvtなど数多くのファセットクラスに継承され、各派生クラスが全てのインターフェースを勝手に定義する。オブジェクト指向プログラミングの観点に立てばこれらが共通の基底クラスを持つ必然性は無く、教科書が悪例とするものではないか(Herb Sutter, Exceptional C++, Boston, Addison-Wesley, 2000; Boston, Addison-Wesley, 2002, pp.80-82)。サンプルソースコードはcodecvtファセットクラスの一つcodecvt_utf8_utf16<wchar_t>へのポインタをnewするが、deleteはどこへ行ったのか。クラス名や関数名に馴染みのない単語が並ぶ。streamlocaleはともかくfacetimbueとはつまり一体何なのか。rdbufに至っては何をか言わんや。

C言語標準入出力の非同期化

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 13.2.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符号長より長い文字が混在すると複雑な事態を招くためと考えられる(サイト作成者の見解)。

次のサンプルコードでこれらを確認する事ができる。

#include <iostream>
#include <ext/stdio_sync_filebuf.h>
#include <ext/stdio_filebuf.h>
#include <boost/core/demangle.hpp>
template<typename T> void CheckStreamBuf(const T& strm)
{
using char_type=typename T::char_type;
using traits_type=typename T::traits_type;
using basic_filebuf=std::basic_filebuf<char_type,traits_type>;
using stdio_sync_filebuf=__gnu_cxx::stdio_sync_filebuf<char_type,traits_type>;
using stdio_filebuf=__gnu_cxx::stdio_filebuf<char_type,traits_type>;
static_assert
(!std::is_base_of<basic_filebuf,stdio_sync_filebuf>::value
,"Something wrong, stdio_sync_filebuf shall not be derived from basic_filebuf.");
static_assert
(std::is_base_of<basic_filebuf,stdio_filebuf>::value
,"Something wrong, stdio_filebuf shall be derived from basic_filebuf.");
std::cout<<"The stream is "<<boost::core::demangle(typeid(T).name())<<", which has ";
if (auto* strmbuf=strm.rdbuf())
{
if (dynamic_cast<stdio_sync_filebuf*>(strmbuf))
{std::cout<<boost::core::demangle(typeid(stdio_sync_filebuf).name());}
else if (dynamic_cast<stdio_filebuf*>(strmbuf))
{std::cout<<boost::core::demangle(typeid(stdio_filebuf).name());}
else
{std::cout<<"an unexpected streambuf";}
}
else
{
std::cout<<"no streambuf";
}
std::cout<<"."<<std::endl;
}
#define CHECKSTREAMBUF(X) std::cout<<"[" #X "] ";CheckStreamBuf(X);
void CheckStreamBufAfterSyncWithStdio(bool sync)
{
std::ios_base::sync_with_stdio(sync);
std::cout<<"Called sync_with_stdio("<<std::boolalpha<<sync<<")"<<std::endl;
CHECKSTREAMBUF(std::cout);
CHECKSTREAMBUF(std::cin);
CHECKSTREAMBUF(std::wcout);
CHECKSTREAMBUF(std::wcin);
std::cout<<std::endl;
}
int main()
{
// The code calls sync_with_stdio after standard output operations, however,
// that is not good because the standard says the effect of such the case is
// implementation-defined (N4659 30.5.3.4/p2). Actually its source shows
// only 'false' given is effective to work. Giving 'true' has no effect.
// https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B98/ios_init.cc#L149-L200
CheckStreamBufAfterSyncWithStdio(true);
CheckStreamBufAfterSyncWithStdio(false);
return 0;
}

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++20でも非推奨のままに残るが(JTC1/SC22/WG21 N4861 D.20.1)将来が心配される。C++17ではcodecvt<char16_t,char,mbstate_t>がその代替とするが、これはwcout、wcin(より一般には内部型wchar_t外部型charのファイルストリームバッファを用いるストリーム)に使えない。C++20はchar8_tの追加でcodecvt<char16_t,char8_t,mbstate_t>が代替となる。

codecvtファセットを自分で作る

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)のままでコンパイルされているとする。

#include <iostream>
#include "TMyCodeCvt.h"
int main()
{
std::ios_base::sync_with_stdio(false);
auto loc=std::locale{std::locale{},new TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::ShiftJIS>>{}};
std::wcout.imbue(loc);
std::wcin.imbue(loc);
auto ans=std::wstring{};
std::wcout<<L"何か入力してください。"<<std::endl;
std::wcin>>ans;
std::wcout<<ans<<L"が入力されました。"<<std::endl;
return 0;
}

これでUTF-16文字列を正常に入出力できるが文字セットはシフトJISで扱えるものに限定される。NMyEncoding::ShiftJISをNMyEncoding::UTF8に書き換えればUTF-8を表示するコマンドプロンプトあるいはPOSIX互換ターミナルへ標準入出力するが、コマンドプロンプトは2符号長UTF-16文字を表示できず入力も正常に行えない。

TMyCodeCvtは可能な限り汎用性に配慮した。参考として、以下とすればUTF-8文字列をシフトJISを表示するウィンドウズコマンドプロンプで入出力できる。実行文字コードはデフォルト(UTF-8)のままでコンパイルされているものとする。

#include <iostream>
#include "TMyCodeCvt.h"
int main()
{
std::ios_base::sync_with_stdio(false);
auto loc=std::locale{std::locale{},new TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF8,NMyEncoding::ShiftJIS>>{}};
std::cout.imbue(loc);
std::cin.imbue(loc);
auto ans=std::string{};
std::cout<<"何か入力してください。"<<std::endl;
std::cin>>ans;
std::cout<<ans<<"が入力されました。"<<std::endl;
return 0;
}

ウィンドウズデスクトップアプリケーション

本サイトはウィンドウズデスクトップアプリケーション開発にwxWidgetsライブラリを用いる。統合開発環境としてCode::Blocksを選択し、Code::Blocksに付属するwxSmithのRAD(Rapid Application Development)を利用する。wxWidgetsライブラリはマルチプラットフォームターゲットだが、本サイトはあくまでウィンドウズをターゲットとし必要に応じウィンドウズAPIを直接利用する。これらを理由としてコンソールアプリケーションと異なり互換性に重きを置かない。

wxWidgetsライブラリ利用アプリケーションテスト用として、統合開発環境カスタマイズ(1)で導入したウィザードで動作確認用のウィンドウズデスクトップアプリケーションをプロジェクト名WindowsDesktopTestで作成する。WindowsDesktopTestFrame.cppソースコード内のWindowsDesktopTestFrame::OnAboutメンバ関数を書き換えて様々なテストを行う。例えば以下でビルドしてアプリケーション実行すれば、[Help|About]で表示されるダイアログは期待通りHello world!を表示する。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("Hello world!",_("Welcome to..."));
}

8ビット符号文字列

8ビット符号文字列をテストするが、結果は物理ファイルを記述する文字コードおよびコンパイラオプション(入力文字コード、実行文字コード)に依存する。ソースコード変更を伴わないコンパイラオプション変更を実行ファイルへ反映するにはそれぞれにおいて[Build|Rebuild]する。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("こんにちは世界!",_("Welcome to..."));
}
物理ファイル 入力文字コード 実行文字コード コンパイル 表示
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コンストラクタへ渡されるためと考えられる。これはコンパイラの意図しない動作で避けるべきであろう。以降においてこの例外は考慮から外し、物理ファイル文字コードと入力文字コードは常に一致するとして"入力文字コード"で両者を総称する。

覚え書き
ただし日本語ウィンドウズにおけるCode::Blocksインストール時のエディタ文字コードデフォルトはCP932であり、mingw-w64の入力文字コードデフォルトはUTF-8である。つまりデフォルトで両者は不整合にある。

実行文字コードUTF-8としてUTF-8文字列を渡す場合はwxStringの変換コンストラクタに頼れず、明示的な変換をする。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox(wxString::FromUTF8("こんにちは世界!"),_("Welcome to..."));
}

入力文字コードUTF-8とすればシフトJIS外の文字も直接使用できる。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox(wxString::FromUTF8("😀😁😂😃"),_("Welcome to..."));
}

16ビット符号文字列

16ビット符号文字列をテストする。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox(L"こんにちは世界!",_("Welcome to..."));
}

ワイド実行文字コードがデフォルト(UTF-16)であればコンパイルは成功し表示も正常となる。wxStringはconst wchar_t*からの変換コンストラクタを持ち、変換される文字列はウィンドウズでUTF-16となる。入力文字コードをUTF-8とすればシフトJIS外の文字も直接使用できる。

void WindowsDesktopTestFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox(L"😀😁😂😃",_("Welcome to..."));
}

wxSmithからの入力

wxWidgetsライブラリを利用するソースコードの文字列は_マクロで囲まれることが多いが、その実体はwxString型を引数とするwxGetTranslation関数で上記の検討結果は変わらない。これらはwxWidgetsライブラリの用意する国際化機能の一部を構成するものであり、_マクロはwx\translation.hに定義されている。

// gettext() style macros (notice that xgettext should be invoked with
// --keyword="_" --keyword="wxPLURAL:1,2" options
// to extract the strings from the sources)
#ifndef WXINTL_NO_GETTEXT_MACRO
#define _(s) wxGetTranslation((s))
#define wxPLURAL(sing, plur, n) wxGetTranslation((sing), (plur), n)
#endif

wxSmithで入力される文字列もこれに従い_マクロで囲んでソースコードへ挿入される。メインウィンドウのwxSmithリソースファイル(wxsmith\WindowsDesktopTestFrame.wxs)をCode::Blocksエディタで開くとwxSmithエディタに読み込まれる。wxSmithエディタ上部ツールホルダーパネルにあるメニューバー(wxMenuBar)アイコンをダブルクリックして[MenuBar editor]ダイアログを開く。[Content]ツリービューのHelpというラベルを持つノードを選択し、[Options|New]を押してNew Menuというラベルを持つ新たなメニューアイテムを作成し、[Options|Label]でそのラベルを日本語メニューに変更して[OK]を押す。WindowsDesktopTestFrame.cppソースコードのメインウィンドウコンストラクタにメニューアイテムが追加されていることを確認する。

WindowsDesktopTestFrame::WindowsDesktopTestFrame(wxWindow* parent,wxWindowID id)
{
...
MenuBar1->Append(Menu1, _("&File"));
...
MenuBar1->Append(Menu2, _("Help"));
...
MenuBar1->Append(Menu3, _("日本語メニュー"));
...
}

実行文字コードCP932なら[日本語メニュー]が正常にメニューバーに表示される。実行文字コードUTF-8では文字化けするが、以下いずれかの修正で正常に表示される。

WindowsDesktopTestFrame::WindowsDesktopTestFrame(wxWindow* parent,wxWindowID id)
{
...
MenuBar1->Append(Menu3, _(L"日本語メニュー"));
...
}
WindowsDesktopTestFrame::WindowsDesktopTestFrame(wxWindow* parent,wxWindowID id)
{
...
MenuBar1->Append(Menu3, _(wxString::FromUTF8("日本語メニュー")));
...
}

これらの修正はしかし、wxSmithエディタで編集を行うたびにリセットされてしまう。別解として上記修正は行わず、_マクロを以下いずれかに修正定義してしまうことも考えられる。国際化機能を使わないと決めてしまえば、それぞれのwxGetTranslation関数コールは省略できる。

#undef _
#define _(s) wxGetTranslation(L ## s)
#undef _
#define _(s) wxGetTranslation(wxString::FromUTF8(s))

wxSmithの文字コードはUTF-8でシフトJIS外の文字も入力できる。入力文字コードをUTF-8とすれば、[日本語メニュー]ラベルをwxSmithで😀😁😂😃に変更しても正常にソースコードに挿入され、_マクロ修正定義で正常表示される。

WindowsDesktopTestFrame::WindowsDesktopTestFrame(wxWindow* parent,wxWindowID id)
{
...
MenuBar1->Append(Menu3, _("😀😁😂😃"));
...
}

_マクロ修正定義はしかし、ライブラリの標準的な使用方法を大きく逸脱し推奨できない。

日本語アプリケーションをどのように開発するか

特にウィンドウズデスクトップアプリケーションにおける開発を検討する。

日本語文字列は全て16ビット符号文字列(UTF-16)として、シフトJIS外文字をソースコード内に書き込む必要があるなら入力文字コードをUTF-8とすれば良い。しかしながらwxSmithはユニコード文字列(ただしUTF-8)を入力できるのに、その文字列はソースコードにおいてシステムロケールの決定する文字コード(日本語ウィンドウズはCP932)として扱われる。これはwxSmithの大きな設計ミスと思われ、wxSmithを利用して日本語アプリケーションを開発するにはこの課題をクリアする必要がある。三つの方法を提案する。

  • 実行文字コードをCP932とする。
  • 実行文字コードをUTF-8とし_マクロ修正定義する。
  • 英語アプリケーションを開発し国際化機能に委ねる。

実行文字コードをCP932とする

  • ソースコードの物理ファイル文字コードをCP932とする。
  • コンパイルオプションに-finput-charset=CP932と-fexec-charset=CP932を加える。

最もシンプルな解決法だが以下のデメリットを持つ。

  • シフトJIS外文字を利用できない。
  • システムロケールを日本語としない環境で文字化けを起こす。ウィンドウズはユニコード化でシステムロケールに関係なく任意の言語を扱えるが、その恩恵にあずからない。

実行文字コードをUTF-8とし_マクロ修正定義する

  • ソースコードの物理ファイル文字コードをUTF-8とし、コンパイルオプションの文字コードをデフォルトのままとする。
  • 先に提示したいずれかの_マクロ修正定義をwxSmithが生成するソースコードに書き加える。

シフトJIS外文字もwxSmithから入力でき、システムロケールと無関係に正しい表示が行える。ただし以下のデメリットを持つ。

  • ライブラリ供給のマクロを勝手に置き換えるのは推奨されない。
  • _マクロに16ビット符号文字列を渡すとコンパイルエラーとなる。

英語アプリケーションを開発し国際化機能に委ねる

  • 全ての文字列を英語で記述しソースコードに書き込む文字を基本ソース文字セット(N4659 5.3/p1)に限定する。
  • GNU gettextを利用する国際化機能で英語文字列を実行時に日本語文字列に置き換える。

アプリケーション開発時に文字コードの問題から完全に自由となる。様々な開発ツールを安心して利用できるし、Code::Blocksエディタ設定とコンパイルオプションの文字コード不整合さえ問題とならない。さらに多言語対応アプリケーションへ容易に拡張できる。しかし言うまでもなく大きなデメリットがある。

  • 英語を駆使してアプリケーションを開発しなければならない。
  • GNU gettextという外部ツールとwxWidgets国際化機能を理解しなければならない。

サイト作成者は、しかし結局これが最良の解ではないかという不幸な結論に傾きつつある。