パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.7.0.5(5)
ファイル入出力クラスの修正

KTxtEditの高機能化にあたりファイル入出力クラスを修正する。

ファイル入出力クラス(TMyFileOpenSave)を修正し、前のステップ(Scintillaストリームバッファの作成)で修正した文字コードリストクラス(TMyEncodings)と新たに作成したScintillaストリームバッファ(TMyScintillaOStreamBuf/TMyScintillaIStreamBuf)を利用する。コア実装クラス(TMyCoreImpl)の修正は次のステップ(コア実装クラスの修正)で行う。

プログラミング手順

  1. ファイル入出力クラス(TMyFileOpenSave)の修正
  2. TMyFileBufに関する追記
  3. リポジトリ更新とビルドテスト

ファイル入出力クラス(TMyFileOpenSave)の修正

エディットコントロールをwxTextCtrlからTMyScintillaに変更する。コントロール側ストリームバッファをTMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufからTMyScintillaOStreamBuf/TMyScintillaIStreamBufに変更する。文字列の処理単位をwchar_tからcharに変更する。なおTMyFileBufファイルストリームバッファは次項に追記する。

  • TMyFileOpenSave.cppインクルードファイルからTMyLocalAllocStreamBuf.hを除く。インクルードファイルにTMyScintilla.h、TMyScintillaStreamBuf.h、TMyFileBuf.hを加える。
  • TMyFileOpenSaveコンストラクタ仮引数をwxTextCtrl*からTMyScintilla*に変更する。TMyFileOpenSave.hはTMyScintilla先行宣言を加えてTMyScintilla未定義でもインクルード可とする。
  • TMyFileOpenSave::Implコンストラクタ仮引数をwxTextCtrl*からTMyScintilla*に変更する。textCtrl_メンバ変数の型も合わせる。
  • TMyFileOpenSave::Implのbom_スタティックメンバ変数はBOMを納める。旧バージョン(UTF-16)は16ビット1符号wchar_tであったが新バージョン(UTF-8)は8ビット3符号でchar[3]で、変更する。
  • TMyFileOpenSave::ImplのDoOpenメンバ関数とDoSaveメンバ関数は全面的に書き換える。

TMyFileOpenSave.h(修正)

...
class TMyFileOpenSave final
{
...
public:
explicit TMyFileOpenSave(class TMyScintilla* textCtrl);
...
};
...

TMyFileOpenSave.cpp(修正)

...
#include "TMyRegKey.h"
#include "version_macro.h"
#include "MyUtility.h"
#include "TMyEncodings.h"
#include "TMyOptionDialog.h"
#include "TMyScintilla.h"
#include "TMyScintillaStreamBuf.h"
#include "TMyFileBuf.h"
...
class TMyFileOpenSave::Impl:public TMyOptionDialog::Observer
{
private:
...
private:
static Impl* tHis_;
static constexpr char bom_[]=u8"\uFEFF";
TMyScintilla* const textCtrl_;
...
bool DoOpen(const wxString& path,const TMyFileOption& option)
{
try
{
auto in=std::filebuf{};
in.open(path.wc_str(),std::ios_base::in|std::ios_base::binary);
in.pubimbue(std::locale{std::locale{},TMyEncodings::GetInstance().GetCodeCvt(option.encoding_)});
if (option.byteOrderMark_)
{
for (auto ch:bom_)
{
if (in.sbumpc()!=ch)
{throw std::runtime_error{"No byte order mark that is expected."};}
}
}
else
{
for (auto ch:bom_)
{
if (in.sbumpc()==ch)
{throw std::runtime_error{"Encountered unexpected byte order mark."};}
}
in.pubseekpos(0);
}
auto out=TMyScintillaOStreamBuf{textCtrl_,option.endOfLine_};
std::copy
(std::istreambuf_iterator<char>{&in}
,std::istreambuf_iterator<char>{}
,std::ostreambuf_iterator<char>{&out});
Remember(path,option);
return true;
}
catch (std::exception& e)
{
wxMessageBox(e.what(),MYAPPINFO_NAME,wxCENTER|wxICON_ERROR);
return false;
}
}
bool DoSave(const wxString& path,const TMyFileOption& option)
{
try
{
auto out=TMyFileBuf<char>{};
out.open(path.wc_str(),std::ios_base::out|std::ios_base::binary);
out.pubimbue(std::locale{std::locale{},TMyEncodings::GetInstance().GetCodeCvt(option.encoding_)});
if (option.byteOrderMark_) {for (auto ch:bom_) {out.sputc(ch);}}
auto in=TMyScintillaIStreamBuf{textCtrl_,option.endOfLine_};
std::copy
(std::istreambuf_iterator<char>{&in}
,std::istreambuf_iterator<char>{}
,std::ostreambuf_iterator<char>{&out});
Remember(path,option);
return true;
}
catch (std::exception& e)
{
wxMessageBox(e.what(),MYAPPINFO_NAME,wxCENTER|wxICON_ERROR);
return false;
}
}
...
public:
Impl(TMyScintilla* textCtrl):textCtrl_{textCtrl}
,recencies_{},current_{GetUnnamed()},observers_{}
,regValues_{wxRegKey::HKCU,"Software" "\\" MYAPPINFO_PUBLISHER "\\" MYAPPINFO_NAME "\\" "TMyFileOpenSave"}
{
...
}
...
};
...
TMyFileOpenSave::TMyFileOpenSave(TMyScintilla* textCtrl):pimpl_{new Impl{textCtrl}} {}
...

TMyFileBufに関する追記

TMyFileOpenSave::ImplのDoSaveメンバ関数が用いるTMyFileBufファイルストリームバッファについて追記する。プログラミング手順を記述するものではないので興味が無ければ飛ばして構わない。

Scintillaコンポーネントの内部文字コードは8ビット、本サイトはデフォルトのUTF-8を用いる。ファイル入出力は内部をUTF-8に固定して外部文字コードを選択可能としたいが、C++標準ライブラリのストリームでこれを実現できるだろうか。文字コード変換は基底をcodecvt<char,char,mbstate_t>とするカスタムcodecvtファセットで行える。これを所有するロケールをfilebuf(basic_filebuf<char>)に設定(imbue)する事になるが、ソースコード(自作のcodecvtファセット)に示す内部型外部型1:Nの制約を満たす事が困難で規格上は許されない。例えばUTF-8で通常の日本語文字は3符号長だが、外部文字コードがシフトJISであれば2符号長に変換され、すなわち3:2である。

ただし内部文字コードUTF-16でも2符号長文字(基本多言語面以外の文字)の存在で1:N制約を満たさない。その場合、ソースコード(N:M変換の安全性)に示すようにファイル出力時のバッファ境界に部分符号シーケンスが残るとエラーとなる。告白するが旧バージョンはこれに起因するバグを有し、先頭から4096番目が2符号長文字の第一符号である文字列をファイルに書き込むとエラーとなるが、そのようなケースは稀でバレないと確信して放置した。

このバグは内部文字コードUTF-8で看過できない。日本語文字(3符号長)で構成される長い文字列をファイル出力すると、ほぼ確実にバッファ境界に部分符号シーケンスが残りバグを呈する。TMyFileBufはこういった不具合に対処したファイルストリームで、DoSaveメンバ関数は通常のfilebuf(basic_filebuf<char>)の代わりに使用する。これでバグを解決するが、あくまでもC++規格外の動作である。

リポジトリ更新とビルドテスト

Git for Windowsリポジトリを更新する。ビルド実行しても良いが、他のファイル修正が未実施でコンパイル確認のみでリンクエラーで終わる。

UMLクラス図

説明する物が無いので省略する。