CubePDF シリーズの大改修

先日 CubePDF Utility 0.5.0β のリリースが完了し、数ヶ月にわたる CubePDF シリーズの改修、さらに言えば、去年からスタートさせていた CubeICE を含むキューブ・ソフト初期のソフトウェア大改修プロジェクトがようやくひと段落しました。そこで、この記事では CubePDF シリーズ、特に CubePDF Utility の改修後書を記載していきます。

はじめに

CubePDF シリーズには全部で 6 個のソフトウェアが存在しますが、今回改修した CubePDFCubePDF Utility、それに簡易版 CubePDF Utility として位置付けている CubePDF Page を始めとして、多くのユーザにご利用頂いているようです。特に、CubePDF は単純な累計ダウンロード数であれば 1,000 万を超えるような数字となっており、約 8 年前に最初のバージョンをリリースした時には、ここまで育つとは思いもよらなかったと言うのが正直な感想です。

CubePDF Utility

そんな CubePDF シリーズですが、10 年近い年月が経過する内に、ソフトウェアの保守がどんどん大変になっていくと言う状態が続いていました。また、CubePDF シリーズ内でのソースコードの共有が上手くいっていなかった事や、CubePDF Utility に存在する諸問題を解決するために利用ライブラリを変更したい等、様々な課題を解決するに際して、一度ソースコードを整理しておきたいと思ったのが今回の大改修を実施した理由です。

余談として、本当であれば CubePDF の話を主役に据えたかったのですが、CubePDF に関しては思ったほど書く事がないと言う理由で、今回の主役は CubePDF Utility に譲っています。CubePDF は、私が人生の中で初めてゼロから作成した GUI アプリケーションですが、ソースコードの保守と言う観点で見ると、他のものに比べれば意外と上手くいってる事は嬉しい誤算でした。これは、View 自体はさほど複雑ではない等の理由によるものだろうと思いますが、そう言った点も含めて、いろいろと予想外だったソフトウェアです。

PDFium の採用

CubePDF Utility の改修における大きな変更点の一つとして PDFium の採用が挙げられます。CubePDF Utility は、これまでレンダリングエンジンとして MuPDF .NET Framework 用にラップした PDFLibNet と言うライブラリを使用していましたが、Unicode 版としてビルドされていないためにファイルパスの扱いで問題が発生する事があり、サムネイル画像が表示されない等の原因にもなっていました。

一方 PDFium では、下記のように必要なタイミングで必要なバイトデータを返す関数ポインタを指定するためのインターフェースが公開されています。このインターフェースを利用してファイルの操作自体は C#/.NET 側で行う事により、これまでの懸念事項であったパスの問題を解決する事ができました (PdfiumReader.cs)。

_core = PdfiumApi.FPDF_LoadCustomDocument(
    new FileAccess
    {
        Length    = (uint)_stream.Length,
        GetBlock  = Marshal.GetFunctionPointerForDelegate(_delegate),
        Parameter = IntPtr.Zero,
    },
    password
);

WPF の再入門

CubePDF Utility は、約 6 年前に WPF を用いて作成した初めての GUI アプリケーションでしたが、WPF での代表的な開発パターンである Model-View-ViewModel (MVVM) への理解が足りなかった事が、その後の保守性の悪さに繋がりました(所謂 Fat View、Fat ViewModel 問題)。また、完成したアプリケーションを実際に試してみるとコールドスタートが非常に遅いと言う結果となったため、これ以降の何年もの間、新規プロジェクトにおける WPF の採用を見送る原因にもなりました。キューブ・ソフトで公開している Windows ソフトウェアの中で CubePDF Utility のみスプラッシュ画面を表示しているのも、このためです。

しかし、ここ数年、システムドライブに採用されるストレージが HDD から SSD に変化するのにつれて、コールドスタートの問題は相対的に小さなものとなりました。また、リリース後に何度か実施した調査によると、どうも 利用している Ribbon ライブラリ の影響も大きいと言う結果が出ていたため、今回、利用ライブラリを Fluent.Ribbon に変更する事で改善を試みています*1。こう言った状況の変化もあって WPF 自体を忌避する理由も徐々に薄れているため、2018 年の個人的なテーマの一つとして WPF の再入門を設定し、これまでに CubeRSS Reader の新規作成と CubePDF Utility の改修を行ってきました。

その成果の一つが下図になります。これは CubePDF Utility のメイン画面を表す MainWindow.xaml.cs の改修前後における記述行数の変化を示したものですが、2,120 行 から 0 行 に削減できた事が分かります(Visual Studio による自動生成部分を除く)。もちろん、実際に 1 行も記述せずに実現できている訳ではないのですが、インパクトの大きさを示すのには良い例かなと思って紹介します。

MainWindow.xaml.cs コード行数の変化

今回、個人的にもっとも感動したのは Expression Blend SDK によって提供される Behavior と言う概念でした。WinForms を始めとして、EventHandler ベースの GUI プログラミングはややもすると View がゴチャゴチャする (Fat View) と言う問題点が指摘されますが、この原因の一つに「EventHandler をどこに記述すれば良いのか判断できない」と言うものがあるように思います。自分自身を振り返っても、EventHandler の記述内容を見ると public なプロパティおよびメソッドのみで完結しているので必ずしも MainForm.cs のような場所に記述する必要はないが、それ以外の適当な場所も思いつかないため、結局そこに書いてしまう(そして、どんどんと膨れ上がる)と言う経験が多々ありました。

Behavior と言う概念は、この問題に対して「単一、あるいは一纏まりの View の動作に対して名前を付け、クラス化する」と言う解決策を示してくれました。さらに、これによって「View の動作単位での汎用化」と言う可能性も視野に入るようになり、特に MVVM における Messenger (EventAggregator) パターンと併用する事で、かなり柔軟な記述が可能となってきました。例えば、CubePDF Utility では「メッセージとしてサブ画面用の ViewModel オブジェクトを送信すると、View は ViewModel の型に紐づけられた SubView を生成して DataContext にその ViewModel を設定し、ShowDialog メソッドを実行する」と言う「動作」を予め作成しておく事で、View の個別の生成処理をかなり削減するのに成功しています。

<i:Interaction.Behaviors>
    <xb:DialogBehavior />
    <xb:OpenFileDialogBehavior />
    <xb:SaveFileDialogBehavior />
    <xb:UriBehavior />
    <xb:CloseBehavior />
    <xb:ClosingBehavior Command="{Binding Ribbon.Close.Command}" />
    <my:PasswordWindowBehavior />
    <my:PreviewWindowBehavior />
    <my:InsertWindowBehavior />
    <my:RemoveWindowBehavior />
    <my:MetadataWindowBehavior />
    <my:EncryptionWindowBehavior />
    <my:SettingsWindowBehavior />
    <my:DragFileBehavior Command="{Binding Drop}" />
</i:Interaction.Behaviors>

Behavior と言うライブラリ自体は 6 年前から使用していたのですが、今回、改めて見直す事によって View との連携方法を大きく前進できたのは非常に良かったように思います。

正式リリースに向けて

キューブ・ソフトで公開しているソフトウェアの多くは β や RC を名乗っています。「いつまで β なんだよ」と言う指摘を見かける事もありますが、これは私自身が長年、正式リリースを名乗って良いのかどうか判断できないし自信もない、と感じていたためと言う面がありました。

この問題を克服するために、2017 年から「アプリケーション部分を含めたユニットテストの拡充と CI 環境による継続的なテスト」と言う課題に取り組んできました(参考:圧縮・解凍ソフト CubeICE をゼロから改修)。そして、現時点で振り返ってみると、まだ完璧とは言い切れませんが、ここ 1 年のものが実を結びつつあると言う実感がでてきました。もう少し時間がかかりそうですが、順次、正式リリースと言えるように今後も環境を整えていく予定です。

また、Cube.Pdf プロジェクトに関しては、ライブラリ部分が CubePDF シリーズで必要な範囲しか実装できておらず不十分なため、もう少し頑張って実装したいと言う思いがあります。こちらに関しては、どうしても優先度が下がりがちになってしまうのですが、全体のスケジュールを見ながら上手く進めていきたいと思います。

*1:ただし、Fluent.Ribbon .NET Framework 4 以降でないと利用できないため、.NET 3.5 版では従来の Ribbon ライブラリを使用しています。