パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
Scintillaストリームバッファの作成

KTxtEditの高機能化にあたりファイル入出力補助クラスを修正しストリームバッファを作成する。

新バージョンも旧バージョンを踏襲してファイルストリームバッファで文字コード変換を行う。このためScintillaコンポーネント(厳密にはTMyScintillaコントロールだが薄いラッパーなのでScintillaコンポーネントとして参照する)に入出力するストリームバッファを作成し、ファイルストリームバッファとデータ交換させる。旧バージョンはテキストコントロールにwxTextCtrlを利用したが、記憶領域であるローカルヒープ(HLOCAL)へ直接アクセスする手段が用意されていたためHLOCALへのストリームバッファとして作成した。Scintillaコンポーネントは入出力で記憶領域へのアクセスが異なり、Scintillaストリームバッファとして記憶領域へのアクセスは隠蔽する。

ところでwxTextCtrlとScintillaコンポーネントには大きな差異がある。wxTextCtrlは文字コードとしてUTF-16を利用する16ビットデータを扱うが、ScintillaはUTF-8を利用する8ビットデータを扱う。文字コード変換を行う自作のcodecvtファセットは適切なテンプレート実引数を与えてそのまま利用できるものの、wchar_tを前提とした処理は全てcharに修正する必要がある。ファイルストリームバッファはwfilebuf(basic_filebuf<wchar_t>)ではなくfilebuf(basic_filebuf<char>)であり、新たに作成するScintillaストリームバッファはwstreambuf(basic_streambuf<wchar_t>)ではなくstreambuf(basic_streambuf<char>)を継承する。

本項目はファイル入出力クラス(TMyFileOpenSave)が利用する二つのクラスのうち、文字コードリストクラス(TMyEncodings)を内部文字コードUTF-16からUTF-8へ修正する。もう一つのクラスとしてScintillaストリームバッファ(TMyScintillaOStreamBuf/TMyScintillaIStreamBuf)を作成して旧バージョンのヒープストリームバッファ(TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBuf)に置き換える。TMyFileOpenSaveは次ステップで修正する。

プログラミング手順

  1. 文字コードリストクラス(TMyEncodings)の修正
  2. Scintillaストリームバッファ(TMyScintillaOStreamBuf/TMyScintillaIStreamBuf)の作成
  3. リポジトリ更新とビルドテスト

文字コードリストクラス(TMyEncodings)の修正

codecvtファセットの基底クラスをcodecvt<char,char,mbstate_t>に変更し、内部文字コードをUTF-8に変更する。

TMyEncodings.h(修正)

...
class TMyEncodings final
{
public:
using CodeCvt=std::codecvt<char,char,mbstate_t>;
...
};
...

TMyEncodings.cpp(修正)

...
class TMyEncodings::Impl
{
...
private:
struct CreateCodeCvtMapFunctor
{
CodeCvtMap& map_;
CreateCodeCvtMapFunctor(CodeCvtMap& map):map_{map} {}
template<typename T> void operator()(T)
{
map_.insert(std::make_pair(T::encoding(),std::make_pair
([](){return new TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF8,T>>;}
,T::canhavebom())));
}
};
...
};
...

Scintillaストリームバッファ(TMyScintillaOStreamBuf/TMyScintillaIStreamBuf)の作成

プロジェクトディレクトリにTMyScintillaStreamBuf.hとTMyScintillaStreamBuf.cppを作成して全ターゲットに加える。旧バージョンのTMyLocalAllocStreamBuf.hとTMyLocalAllocStreamBuf.cppとそれほど大きく違わず、コピーして修正した方が早いかもしれない。TMyLocalAllocStreamBuf.hとTMyLocalAllocStreamBuf.cppは不要になるのでプロジェクトから削除する。

  • 入出力で別のクラスとして共にstreambuf(basic_streambuf<char>)を継承する。どちらもpimplイディオムで実装する
  • ファイル側改行データ(CRLF、LF、CR)をScintillaのそれ(CRLF)に変換する。ファイルストリームバッファ側は必ずbinaryで開く。
  • 出力側(TMyScintillaOStreamBuf)はローダー(Scintilla::ILoader)を作成して出力する。デストラクタでドキュメントに変換してScintillaコンポーネントのドキュメントに置き換える。
  • 入力側(TMyScintillaIStreamBuf)はScintillaコンポーネントからドキュメントへのポインタを取得してそこから入力する。
メンバ関数 機能 返値 説明 仮引数 説明
TMyScintillaOStreamBuf コンストラクタ - - TMyScintilla* TMyScintillaコントロール
TMyFileOption::EOL 改行コード
(streambuf継承関数) ... ... ... ... ...
メンバ関数 機能 返値 説明 仮引数 説明
TMyScintillaIStreamBuf コンストラクタ - - TMyScintilla* TMyScintillaコントロール
TMyFileOption::EOL 改行コード
(streambuf継承関数) ... ... ... ... ...

TMyScintillaStreamBuf.h

#ifndef TMYSCINTILLASTREAMBUF_H
#define TMYSCINTILLASTREAMBUF_H
#include "MyInterfaceType.h"
class TMyScintillaOStreamBuf:public std::streambuf
{
private:
using EOL=TMyFileOption::EOL;
private:
MY_PIMPL(std::unique_ptr,ImplBase,pimpl_);
template<EOL> class Impl;
protected:
int_type overflow(int_type c) override;
int sync() override;
public:
explicit TMyScintillaOStreamBuf(class TMyScintilla* ctrl,EOL eol);
TMyScintillaOStreamBuf(const TMyScintillaOStreamBuf& rhs)=delete;
TMyScintillaOStreamBuf& operator=(const TMyScintillaOStreamBuf& rhs)=delete;
TMyScintillaOStreamBuf(TMyScintillaOStreamBuf&& rhs);
TMyScintillaOStreamBuf& operator=(TMyScintillaOStreamBuf&& rhs);
~TMyScintillaOStreamBuf() override;
};
class TMyScintillaIStreamBuf:public std::streambuf
{
private:
using EOL=TMyFileOption::EOL;
private:
MY_PIMPL(std::unique_ptr,ImplBase,pimpl_);
template<EOL> class Impl;
protected:
int_type underflow() override;
public:
explicit TMyScintillaIStreamBuf(class TMyScintilla* ctrl,EOL eol);
TMyScintillaIStreamBuf(const TMyScintillaIStreamBuf& rhs)=delete;
TMyScintillaIStreamBuf& operator=(const TMyScintillaIStreamBuf& rhs)=delete;
TMyScintillaIStreamBuf(TMyScintillaIStreamBuf&& rhs);
TMyScintillaIStreamBuf& operator=(TMyScintillaIStreamBuf&& rhs);
~TMyScintillaIStreamBuf() override;
};
#endif // TMYSCINTILLASTREAMBUF_H

TMyScintillaStreamBuf.cpp

#include "wx_pch.h"
#include "TMyScintillaStreamBuf.h"
#include "TMyScintilla.h"
#include <algorithm>
#include <Scintilla.h>
#include <ILoader.h>
namespace
{
template<TMyFileOption::EOL> constexpr char GetEol();
template<> constexpr char GetEol<TMyFileOption::EOL::LF>() {return '\n';}
template<> constexpr char GetEol<TMyFileOption::EOL::CR>() {return '\r';}
}
class TMyScintillaOStreamBuf::ImplBase
{
public:
virtual int_type overflow(int_type c)=0;
virtual int sync()=0;
public:
virtual ~ImplBase() {};
static ImplBase* Create(TMyScintillaOStreamBuf* tHis,TMyScintilla* ctrl,EOL eol);
};
template<TMyFileOption::EOL N> class TMyScintillaOStreamBuf::Impl:public ImplBase
{
private:
static constexpr int bufSize_=100;
static constexpr int initialBytes_=1024;
TMyScintillaOStreamBuf* const this_;
TMyScintilla* const ctrl_;
Scintilla::ILoader* const loader_;
char buf_[bufSize_];
private:
int AddData(const char* data,Sci_Position length)
{
auto ret=loader_->AddData(data,length);
this_->pbump(this_->pbase()-this_->pptr());
return ret==SC_STATUS_OK?0:-1;
}
public:
int_type overflow(int_type c) override
{
sync();
if (c!=EOF) {*this_->pptr()=c;this_->pbump(1);}
return c;
}
int sync() override
{
static char dataToWrite[bufSize_*2];
auto* pos=dataToWrite;
for (const auto* p=this_->pbase();p!=this_->pptr();++p,++pos)
{
if (*p!=GetEol<N>()) {*pos=*p;}
else {*pos='\r';*(++pos)='\n';}
}
return AddData(dataToWrite,pos-dataToWrite);
}
public:
Impl(TMyScintillaOStreamBuf* tHis,TMyScintilla* ctrl):this_{tHis}
,ctrl_{ctrl}
,loader_{reinterpret_cast<Scintilla::ILoader*>
(ctrl_->SendMsg
(SCI_CREATELOADER,initialBytes_,SC_DOCUMENTOPTION_DEFAULT))}
// Could be SC_DOCUMENTOPTION_TEXT_LARGE to handle documents larger than 2GiB.
,buf_{0}
{
if (!loader_)
{
throw std::runtime_error
{"Failed to get Scintilla ILoader interface."};
}
this_->setp(buf_,buf_+bufSize_);
}
~Impl() override
{
sync();
auto* doc=loader_->ConvertToDocument();
ctrl_->SendMsg(SCI_SETDOCPOINTER,0,(LPARAM)doc);
ctrl_->SendMsg(SCI_RELEASEDOCUMENT,0,(LPARAM)doc);
ctrl_->SendMsg(SCI_SETUNDOCOLLECTION,true,0);
// Since SCI_CREATELOADER do SCI_SETUNDOCOLLECTION(false), see src/Editor.cxx:8108.
ctrl_->SendMsg(SCI_EMPTYUNDOBUFFER,0,0);// Not sure this is required.
}
};
template<> int TMyScintillaOStreamBuf::Impl<TMyFileOption::EOL::CRLF>::sync()
{
return AddData(buf_,this_->pptr()-this_->pbase());
}
TMyScintillaOStreamBuf::ImplBase* TMyScintillaOStreamBuf::ImplBase::Create(TMyScintillaOStreamBuf* tHis,TMyScintilla* ctrl,EOL eol)
{
switch (eol)
{
case EOL::CRLF:
return new Impl<EOL::CRLF>{tHis,ctrl};
case EOL::LF:
return new Impl<EOL::LF>{tHis,ctrl};
case EOL::CR:
return new Impl<EOL::CR>{tHis,ctrl};
default:
assert(false);return nullptr; // Never reach here.
}
}
TMyScintillaOStreamBuf::TMyScintillaOStreamBuf(TMyScintilla* ctrl,EOL eol):pimpl_{ImplBase::Create(this,ctrl,eol)} {}
TMyScintillaOStreamBuf::TMyScintillaOStreamBuf(TMyScintillaOStreamBuf&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyScintillaOStreamBuf& TMyScintillaOStreamBuf::operator=(TMyScintillaOStreamBuf&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
TMyScintillaOStreamBuf::~TMyScintillaOStreamBuf() {}
TMyScintillaOStreamBuf::int_type TMyScintillaOStreamBuf::overflow(int_type c) {return pimpl_->overflow(c);}
int TMyScintillaOStreamBuf::sync() {return pimpl_->sync();}
class TMyScintillaIStreamBuf::ImplBase
{
public:
virtual int_type underflow()=0;
public:
virtual ~ImplBase() {};
static ImplBase* Create(TMyScintillaIStreamBuf* tHis,TMyScintilla* ctrl,EOL eol);
};
template<TMyFileOption::EOL N> class TMyScintillaIStreamBuf::Impl:public ImplBase
{
private:
static constexpr int rangeSize_=100;
static constexpr int putbackSize_=4;
static constexpr int bufSize_=rangeSize_+putbackSize_;
TMyScintillaIStreamBuf* const this_;
char buf_[bufSize_];
const char* pos_;
public:
int_type underflow() override
{
if (*pos_==0) {return EOF;}
if (this_->gptr()>=this_->egptr())
{
auto numPutback=this_->gptr()-this_->eback();
if (numPutback>putbackSize_) {numPutback=putbackSize_;} // std::min makes putbackSize_ ODR-used in Debug config.
auto* tgt=std::copy(this_->gptr()-numPutback,this_->gptr(),buf_);
for (auto posEnd=pos_+rangeSize_;pos_<posEnd&&*pos_;++pos_,++tgt)
{
switch (*pos_)
{
case '\r':
if (*(pos_+1)=='\n') {++pos_;}// Intentionally no break;
case '\n':
*tgt=GetEol<N>();break;
default:
*tgt=*pos_;break;
}
}
this_->setg(buf_,buf_+numPutback,tgt);
}
return *this_->gptr();
}
public:
Impl(TMyScintillaIStreamBuf* tHis,TMyScintilla* ctrl):this_{tHis}
,buf_{0}
,pos_{reinterpret_cast<char*>(ctrl->SendMsg(SCI_GETCHARACTERPOINTER,0,0))}
{
this_->setg(buf_,buf_,buf_);
}
};
template<> class TMyScintillaIStreamBuf::Impl<TMyFileOption::EOL::CRLF>:public ImplBase
{
public:
int_type underflow() override {return EOF;}
public:
Impl(TMyScintillaIStreamBuf* tHis,TMyScintilla* ctrl)
{
auto* buf=reinterpret_cast<char*>(ctrl->SendMsg(SCI_GETCHARACTERPOINTER,0,0));
tHis->setg(buf,buf,buf+ctrl->SendMsg(SCI_GETLENGTH,0,0));
}
};
TMyScintillaIStreamBuf::ImplBase* TMyScintillaIStreamBuf::ImplBase::Create(TMyScintillaIStreamBuf* tHis,TMyScintilla* ctrl,EOL eol)
{
switch (eol)
{
case EOL::CRLF:
return new Impl<EOL::CRLF>{tHis,ctrl};
case EOL::LF:
return new Impl<EOL::LF>{tHis,ctrl};
case EOL::CR:
return new Impl<EOL::CR>{tHis,ctrl};
default:
assert(false);return nullptr; // Never reach here.
}
}
TMyScintillaIStreamBuf::TMyScintillaIStreamBuf(TMyScintilla* ctrl,EOL eol):pimpl_{ImplBase::Create(this,ctrl,eol)} {}
TMyScintillaIStreamBuf::TMyScintillaIStreamBuf(TMyScintillaIStreamBuf&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyScintillaIStreamBuf& TMyScintillaIStreamBuf::operator=(TMyScintillaIStreamBuf&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
TMyScintillaIStreamBuf::~TMyScintillaIStreamBuf() {}
TMyScintillaIStreamBuf::int_type TMyScintillaIStreamBuf::underflow() {return pimpl_->underflow();}

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

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

UMLクラス図

TMyScintillaOStreamBuf/TMyScintillaIStreamBufについて説明するが基本的構造はTMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufのそれと変わらない。