|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
最新のScintillaコンポーネントを自分でビルドしてwxWidgetsライブラリから利用する手順を説明する。
Scintillaはウィンドウズ、リナックスライク(GTKツールキット)、アップルマッキントッシュ(Cocoaフレームワーク)をターゲットとする多機能なエディットコンポーネントである。wxWidgetsにはwxStyledTextCtrlというラッパーコントロールが用意されているが後述の理由で最新版の機能を欠く。内部文字コードがUTF-8デフォルトなのに入出力でwxWidgets文字型(wxString)を経由するのも面白くない。本記事はScintillaを自力でビルドする。これをwxWidgetsで利用するが、Scintillaの機能はラッパーを介さず直接コールする手段を提供して効率化を図る。本記事はバージョン5.3.1を前提に記述する。別に必要となる字句解析ライブラリLexillaはバージョン5.2.0を前提とする
Scintillaは3.7.6でC++14を用いるメインブランチ(4.0.0以降)と用いないLongTerm3ブランチ(3.X.X)に分岐して、バージョン履歴は二つ存在する。
wxWidgetsライブラリは現時点でC++14でビルドされる一方で、Scintillaメインブランチは4.0.3でC++17にスイッチした。これを理由にwxStyledTextCtrlはLongTerm3ブランチか4.0.3(確実には3.7.6)より前のScintillaを用いざるを得ない。LongTerm3は3.21.1で更新停止して、つまりwxStyledTextCtrlは最新版のScintillaを用いる事は現時点で不可能となった。
2022年11月時点でwxStyledTextCtrlの用いるScintillaバージョンをwxStyledTextCtrl::GetLibraryVersionInfoスタティックメンバ関数で確認した。MSYS2供給wxWidgetsはバージョン3.0.5でScintillaの3.21を用いる。そのwxStyledTextCtrlは日本語IME入力ウィンドウを必ずスクリーン左上隅に表示するという不具合を持つ。最新のwxWidgetsは3.2.1でScintillaの3.7.2を用いて、なぜか3.0.5のそれより古い。いずれにせよScintillaの最新版に遥かに遅れる。
Scintillaは4.0.3から字句解析(lexer)部分をLexillaと名付ける別ライブラリに分離し、5.0.0からプロジェクト自体を分離した。本記事は両者を共に導入するものの、それぞれが別プロジェクトとしてリリースされ異なるバージョン番号を持つため、ルートディレクトリを共有としながら異なるディレクトリに展開する。
リンク先からscintilla531.zipとlexilla520.zipをダウンロードする。
ディレクトリは以下とする。異なるバージョンのソースコード展開ディレクトリはScintillaルートの配下に並列させる。
| 目的 | ディレクトリ |
|---|---|
| Scintillaルート | C:\Scintilla |
| Scintillaソースコードの展開 | C:\Scintilla\scintilla531 |
| Scintillaビルドの出力 | C:\Scintilla\scintilla531\my_builds\[ビルド名] |
| Lexillaソースコードの展開 | C:\Scintilla\lexilla520 |
| Lexillaビルドの出力 | C:\Scintilla\lexilla520\my_builds\[ビルド名] |
それぞれのzipファイルをScintillaルートに置きエクスプローラー右クリック[すべて展開]で[圧縮(ZIP)形式フォルダーの展開]ダイアログ[ファイルを以下のフォルダーに展開する]をC:\Scintillaに変更して[展開]する。デフォルトのままではscintilla/lexillaディレクトリをルートとして圧縮されているため一階層余分になる。展開されるディレクトリ名はバージョン番号を含まないscintilla/lexillaなので、これをscintilla531/lexilla520に変更する。本記事での相対パスはC:\Scintilla\scintilla531あるいはC:\Scintilla\lexilla520からとする。なお、後述のLexillaビルド用Scintillaライブラリインクルードを含むscintillaディレクトリが既に存在している場合は展開する前に一時的別名に付け替えて、新たに展開作成さるscintillaディレクトリの名前を変更後に元に戻す。また両者のincludeサブディレクトリの内容が同一である事を確認し、異なれば後者から前者へコピーする。
| ディレクトリ | ファイル | 内容 |
|---|---|---|
| C:\Scintilla | ||
| ├ scintilla531 | README | インストール方法の記述 |
| ││ | ... | ... |
| │├ bin | (デフォルト出力ディレクトリ) | |
| │├ doc | ... | (ドキュメントディレクトリ) |
| │├ include | *.h | ライブラリインクルード |
| ││ | Scintilla.iface | インターフェース定義 |
| │├ src | .cxx,.h | ソースコード、インクルード |
| │├ win32 | makefile | MinGW用メイクファイル |
| ││ | scintilla.mak | msvc用メイクファイル |
| ││ | ... | ... |
| │︙ | ... | ... |
| └ lexialla520 | README | インストール方法の記述 |
| │ | ... | ... |
| ├ bin | (デフォルト出力ディレクトリ) | |
| ├ doc | ... | (ドキュメントディレクトリ) |
| ├ include | *.h | ライブラリインクルード |
| │ | LexicalStyles.iface | インターフェース定義 |
| ├ src | .cxx,.h | ソースコード、インクルード |
| │ | makefile | MinGW用メイクファイル |
| │ | lexilla.mak | msvc用メイクファイル |
| │ | ... | ... |
| ︙ | ... | ... |
ビルドはMSYS2ツールで行う。最も簡単にはmingw32/mingw64ターミナルから、Scintillaであればwin32ディレクトリで、Lexillaであればsrcディレクトリで、mingw32-makeを実行すればそれぞれのbinディレクトリにダイナミック/スタティックリンクライブラリファイルを得る。READMEはオプション設定方法を全く記述しないため環境変数による設定方法を推測するが、正確にはそれぞれのメイクファイルを参照いただきたい。
| 環境変数 | 内容 | 説明 |
|---|---|---|
| DEBUG | ビルドターゲット指定 | 空白でない任意文字列ならデバッグ(-g)、それ以外はリリース(-Os) |
| DEFINES | コンパイルの追加オプション | 主にマクロ定義(-D)用 |
| BASE_FLAGS | コンパイルの追加オプション | - |
| CXXFLAGS | コンパイルとリンクの追加オプション | - |
| RANLIB | ranlib(書庫インデックス作成)指定 | デフォルトはranlib |
| WINDRES | windres(リソースコンパイラ)指定 | Scintillaのみ、デフォルトはwindres |
デフォルトで出力先は固定され、オブジェクトファイルはwin32/srcディレクトリ、ライブラリファイルはbinディレクトリに出力される。以下に各ビルドのオプションをまとめるが、従ってデフォルトのままでは共存できない。
| ビルド名 | サブシステム | オプション |
|---|---|---|
| debug32 | mingw32 | -g |
| release32 | -Os -s | |
| debug64 | mingw64 | -g |
| release64 | -Os -s |
本サイトは、Scintillaであればwin32ディレクトリに、Lexillaであればsrcディレクトリに、my_builds.batバッチファイルを作成して実行する。my_builds.batはScintillaとLexillaで共通化できるが、Lexillaには一つの問題が残り後述する。my_builds.batはbinディレクトリを用いず、my_buildsディレクトリを追加して各ビルドはその配下に出力して共存する。my_builds.batは最大一つの仮引数を取りmingw32-makeへターゲット名として渡す。my_builds.batはインポートライブラリファイル(mingw-w64はDLLに直接リンクできるので必ずしも必要としない)も作成する。このファイルはメイクファイルの定義外なのでcleanターゲットで消去されないが、my_builds.batは実引数にcleanを与えた場合にmy_buildsディレクトリを消去するので問題とならない。
Lexillaに一つの問題が残る。LexillaはScintillaライブラリインクルードを必要とするが、メイクファイルにパスが../../scintilla/include/Scintilla.hのように直接書き込まれている。本サイトは複数バージョン共存の備えとしてscintillaをscintilla531に変更したため、このままではScintillaライブラリインクルードを発見できない。対処としてC:\Scintilla\scintilla\includeディレクトリを作成してC:\Scintilla\scintilla531\includeをコピーした。これらのファイルがScintilla更新で変更される可能性を否定できない以上、将来に禍根を残した。
my_builds.batが出力ディレクトリ(my_builds)に生成するファイルの配置を示す。
| ディレクトリ | ファイル | 内容 |
|---|---|---|
| C:\Scintilla | ||
| ├ scintilla531 | ... | ... |
| │├ bin | (デフォルト出力先ディレクトリ、使用しない) | |
| │︙ | ||
| │├ include | *.h | ライブラリインクルード |
| │├ src | ... | ... |
| │├ win32 | makefile | MinGW用メイクファイル |
| ││ | my_builds.bat | ビルトバッチ |
| ││ | ... | ... |
| │├ my_builds | ||
| ││├ debug32 | ||
| │││├ obj | *.o | オブジェクト |
| │││└ lib | libscintilla.a | スタティックリンクライブラリ |
| │││ | Scintilla.dll | ダイナミックリンクライブラリ |
| │││ | Scintilla.dll.a | インポートライブラリ |
| ││├ release32 | ||
| ││︙ | ... | ... |
| ││├ debug64 | ||
| │││︙ | ... | ... |
| ││└ release64 | ||
| ││ ︙ | ... | ... |
| │︙ | ||
| ├ scintilla | ||
| │└ include | *.h | Lexillaビルド用Scintillaライブラリインクルード |
| └ lexilla520 | ... | ... |
| ├ bin | (デフォルト出力先ディレクトリ、使用しない) | |
| ︙ | ||
| ├ include | *.h | ライブラリインクルード |
| ├ src | makefile | MinGW用メイクファイル |
| │ | my_builds.bat | ビルトバッチ |
| │ | ... | ... |
| ├ my_builds | ||
| │├ debug32 | ||
| ││├ obj | *.o | オブジェクト |
| ││└ lib | liblexilla.a | スタティックリンクライブラリ |
| ││ | liblexilla.dll | ダイナミックリンクライブラリ |
| ││ | liblexilla.dll.a | インポートライブラリ |
| │├ release32 | ||
| ││︙ | ... | ... |
| │├ debug64 | ||
| ││︙ | ... | ... |
| │└ release64 | ||
| │ ︙ | ... | ... |
| ︙ |
最新のScintillaをwxWidgetsライブラリと共に使用するための薄いラッパー(thin wrapper)としてTMyScintillaクラスを作成する。これはwxControlを継承して通常のwxWidgetsコントロールとして振る舞うが、機能は全てScintillaコンポーネントを直接操作する。wxWidgetsのウィンドウズ実装部を利用したため他の実装には使えない。サンプルコードはこのラッパーを利用する。
ウィンドウズ実装のScintillaコンポーネントはSendMessageウィンドウズAPI関数によるメッセージ送出で操作する。TMyScintillaはこれを実行するSendMsgメンバ関数のみを用意する。
| メンバ関数 | 機能 | 戻り値 | 説明 | 仮引数 | 説明 |
|---|---|---|---|---|---|
| TMyScintilla | コンストラクタ | - | - | - | - |
| TMyScintilla | コンストラクタ | - | - | (Create参照) | (Create参照) |
| Create | 2ステップ構築用 | bool | 構築成功 | wxWindow* parent | 親コントロール |
| wxWindowID id | ウィンドウID | ||||
| const wxPoint& pos=wxDefaultPosition | 位置 | ||||
| const wxSize& size=wxDefaultSize | サイズ | ||||
| long style=0 | (未使用) | ||||
| const wxValidator& validator=wxDefaultValidator | (未使用) | ||||
| const wxString& name="TMyScintilla" | コントロール名 | ||||
| SendMsg | メッセージ送出 | LRESULT | メッセージ処理結果 | UINT msg | メッセージID |
| WPARAM wParam | 追加のメッセージ固有情報 | ||||
| LPARAM lParam | 追加のメッセージ固有情報 | ||||
| GetVersionNumber | バージョン番号取得 | int | Scintillaバージョン番号 | - | - |
| (wxControl継承関数) | ... | ... | ... | ... | ... |
ウィンドウズ実装のScintillaコンポーネントはイベント発生でWM_NOTIFYを送出し、lParamに情報を格納するSCNotification構造体へのポインタを渡す。TMyScintillaはWM_NOTIFYをwxEVT_MY_SCINTILLA_NOTIFYカスタムイベントに置き換えてwxCommandEventを送出する。wxCommandEventインスタンスにはwxCommandEvent::SetClientDataメンバ関数でSCNotification構造体へのポインタを格納する。
| カスタムイベント | 説明 | 送出 | 説明 |
|---|---|---|---|
| wxEVT_MY_SCINTILLA_NOTIFY | Scintillaイベント | wxCommandEvent | GetClientDataメンバ関数でSCNotification構造体ポインタを取得できる |
wxWidgetsとメッセージループの知見でコーディングを大いに節約する。
大きなファイルを折り返し表示すると不具合を発生する事があるため、できればそのような運用は避けた方が良い。
Scintillaコンポーネントは折り返し表示オフがデフォルトで、SCI_SETWRAPMODEメッセージでオンに変更して表示幅を超える文字列を折り返し表示できる。折り返し処理はイベントによるバックグラウンドで行われるが、ウィンドウズ実装で巨大なファイルを読み込んだ場合には不具合を呈する。例えば検証パソコン、Release64ターゲットのプログラムで約20MiBの日本語UTF-8ファイルを読み込んだ場合、バックグラウンド処理の完了におよそ16分を要し、その間不具合が継続する。キー(矢印キーを除く)、マウス、スクロールバー操作は可能だがwxWidgetsアイドルイベントを受け付けず、アプリケーションは終了できない。
これはScintillaアイドルイベントが過剰に生成されるためで、開発者はバグと認識するも長い間放置している。折り返し処理はScintillaアイドルイベントを捉えてバックグラウンドで逐次実行するが、その負荷は当然ながらファイルサイズに依存する。Scintillaアイドルイベントは周期的なタイマーイベントをトリガーとして一定期間に連続して大量生成する。これはイベント処理待ちが空となると一度だけ生成するwxWidgetsアイドルイベントと異なる。この過剰なScintillaアイドルイベントが消費されない限りwxWidgetsアイドルイベントは生成されない。
バックグラウンド折り返し処理中にファイル末尾の付近で矢印キーによるテキストカーソル移動を行うと、処理が完了するまで全てのイベントが遅延される。すなわち全ての操作を受け付けずハング状態となる。これはテキストカーソル移動が残った折り返し処理の全てをフォアグラウンドで実行するためで、その間は他のイベント処理を行わない。該当するソースコードを示す(src\Editor.cxx:855)が、テキストカーソル位置が折り返し処理ペンディングの位置にある場合(currentLine >= wrapPending.start)に全領域を対象に折り返し処理を実行する(WrapLines(WrapScope::wsAll))。
WrapLines(WrapScope::wsAll)を可視部の処理に限定するWrapLines(WrapScope::wsVisible)に変更する、あるいはそもそも描画ルーチン(src\Editor.cxx:1744)がWrapLines(WrapScope::wsVisible)をコールするのでif (currentLine>=wrapPending.start) {...}を消去してしまう、のいずれかで解決できそうだが、現時点ではバグレポートに留め結論としていない。
Code::Blocksから利用するための設定を示す。パスはDebug32ターゲットを例とするが、他のターゲットもこれに準ずれば良い。
[Linker settings]ページの[Link libraries]リストボックスでScintillaのライブラリファイルを追加する。ダイナミックリンクの場合はScintilla.dllとliblexilla.dllを配布に含む必要がある。スタティックリンクの場合はウィンドウズAPIライブラリを追加する。
| リンク | ライブラリファイル | 追加ウィンドウズAPI |
|---|---|---|
| ダイナミック | Scintilla.dll.a, liblexilla.dll.a | - |
| スタティック | libscintilla.a, liblexilla.a | libImm32.a, libmsimg32.a |
[Search directories]ページの[Compiler]ページ、[Linker]ページ、[Resource compiler]ページ各リストボックスの探索パスに以下を追加する。[Resource compiler]への追加は不要と思われるが、何も考えずに[Compiler]と同じとした。
| ページ | 探索パス |
|---|---|
| [Compiler] | C:\Scintilla\scintilla531\include |
| C:\Scintilla\scintilla531\win32 | |
| C:\Scintilla\lexilla520\include | |
| [Linker] | C:\Scintilla\scintilla531\my_builds\debug32\lib |
| C:\Scintilla\lexilla520\my_builds\debug32\lib | |
| [Resource compiler] | C:\Scintilla\scintilla531\include |
| C:\Scintilla\scintilla531\win32 | |
| C:\Scintilla\lexilla520\include |