ini ファイルの Section の有無を確認

レジストリを汚したくない気持ちと、ini ファイルの手軽さから、僕はパラメータの設定を、いつも ini ファイルに記録してしまう。

今回、あるプログラムを見直していて、これまで ini ファイル内のセクション自体の存在を「確認」するようなプログラムを書いた経験がないことに気がついた。

ini ファイル内に「XXX」という名前のセクションが、あるか・ないか?
これは、それを確認する方法の覚え書き。

【もくじ】

1.もし、セクションがなかったら?
2.セクションの存在を確認
3.まとめ
4.お願いとお断り

1.もし、セクションがなかったら?

この秋から、マークシートリーダーのプログラムを見直す作業を、ヒマを見つけては行ってきた。
なぜ、そんな作業をすることにしたかというと、読み取り速度に目をつぶってもいいから、ファイル容量的に軽い(=小さな)マークシートリーダーも用意したくなったというのがその理由。

そもそも、自作のマークシートリーダーを作ろうと思い立ったのが、2020年の2月。最初はDelphi用のOpenCVライブラリを利用。読み取り速度より何より、「読めるかどうか」それすらわからない中でのチャレンジ。読み取り性能に重きを置いたのは言うまでもない。結果、読み取り性能的には十分満足できる、夢見た通りのプログラムを完成させることができた。その動作速度は(PCの性能にも左右されるが、My環境では)1枚あたり1200マークあるシートを、1枚あたり約1.2秒で処理。これだと40名分のマークシート読み取りに48秒、200名分ならば6分。

MyPCのスペック・・・プロセッサ:11th Gen Intel(R) Core(TM) i7-1185G7 3.00GHz / 実装RAM 32GB

その後、がむしゃらに高速化を目指した時期があって、その学びの中で、僕は embeddable Python の存在を知った。これにPython用のOpenCVその他のライブラリをインストールして、Pythonの力を借りることで上記のシートを0.34秒/枚、40名分を14秒以下、200名分を1分強で読み取れるマークシートリーダーへ、プログラムは進化。きちんとマークしてあれば、読み取りミスはもちろん「0」。マークの濃さや塗りつぶし面積をさまざまに変えて、読み取りパラメータの設定を調整した結果、相当薄いが明らかにマークしてあるものや、逆に、消し方が不十分でうっすらとマークの痕跡が残ったものにもそれなりに対応、さらに、読み取り結果をヒトと協働して確認できるチェック機能も搭載(非力なPCではややもっさりした動作になるが・・・)。ついでに音声読み上げ機能も欲しくなり、Windowsに標準搭載されている日本語の音声合成エンジン(Microsoft Haruka Desktop)のHarukaさんにも登場してもらって(Windowsの機能の利用だから著作権的には問題ないと思うんだけど・・・)ユーザーが選択した任意のシートのマーク読み取り結果を、簡単にチェックできるプログラムとした。

性能的な部分では、十分、満足した・・・けど、そのかわり、プログラム全体のサイズは超巨大化。exe それ自体は3.81MB程度なのだが、Python用のOpenCVをインストールしたフォルダの容量が、なんと158MB!(フォルダのプロパティで確認したところ、ファイル数: 2,820、フォルダー数: 283 とのこと)

これら、プログラムの動作に必要な一式をZipファイル一つにまとめて、他のPCにコピー&展開すると、PCによってはその展開(解凍)処理だけで20分くらいかかってしまう・・・。ライブラリには相当数、このプログラムには必要のないファイルも含まれているはず、そう思えても、どれが不要なファイルなのかが僕にはわからない。

(多少、動作速度は遅くてもよいから、サクっとコピーして、すぐに動かせるマークシートリーダーも欲しい・・・ )

ってか、exeのある場所にPython環境があれば、Python環境を利用して高速動作、Python環境がなければ(自動的に)Delphi用のOpenCVを利用して動作するような、Python環境を「後付けで追加することも可能」(ユーザーが動作環境を選べる)ようなカタチにしたいって、いつの間にか思い始めて・・・。

そのような経緯から僕は、プログラムの起動設定の見直し作業に着手した。それが冒頭に書いたようにこの秋の始まりの頃だった。

まず、行ったことはPython環境の完全な切り離し。せっかく、くっつけたものを切り離すのは大変だったけど、あっちこっちをいじくりまわして、なんとか切り離しに成功☆ Delphi用のOpenCVだけで動作する状態に戻せた。

内部的にはPython環境を利用する際に必要なコードはすべて残してあるので、次に exe と同じ場所にPython39-32フォルダがあった場合には、Python環境を利用して動作するようにプログラムを修正。これも何とか実現できた。

次に、ミニマム構成でプログラムを動かすために最低限必要なDLL等を確認。実際にミニマム(と思える)構成を作ってみて、動作テストを繰り返し行った。その中で、起動時に設定する読み取りパラメータに関して解決しておいた方がいい問題があることを発見。

Delphi用とPython用のOpenCVでは、起動時に設定するパラメータの一部が異なっている。Python環境の有無で(具体的にはPython39-32フォルダの有無で判断)、当然デフォルト・パラメータ設定を変えて起動させなければならない。その部分のプログラムを見直していて ini ファイルに「もし、読みだすべきセクション(名)そのものがなかったら?」という場合も想定しておいた方がいいことに、僕は初めて気がついた。

ちなみに、これまで書いてきたのは次のコード。これでも第2引数に指定したキーがなかった場合に加え、第1引数に指定したセクションそのものがなかった場合にも、第3引数に指定したデフォルト値が変数にセットされるから、エラーにはならないのだけれど・・・、ユーザーには「セクションそのものがない」ということが伝わらない。ユーザーがデフォルト値として設定されたパラメータを調整・保存して初めて ini ファイルに「Section1」が生まれる・・・。

uses
  System.IniFiles;

procedure TFrmMain.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  str01, str02: String;
begin
  //iniファイルからデータを読込み
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    str01:=Ini.ReadString('Section1', '文字列型_XXX', 'ABC');
    str02:=Ini.ReadString('Section1', '文字列型_YYY', 'DEF');
  finally
    Ini.Free;
  end;
  AAA.Text:=str01;
  BBB.Text:=str02;
end;

つまり、これまでの僕のプログラムは、各パラメータ値の設定とデフォルト・パラメータの設定を記録したセクションが「必ず ini ファイル内にある」という大前提で動いていたわけだ。Ini.ReadString の第3引数で「セクション」や「キー」がなかった場合のデフォルト値を指定してあるから、ini ファイル内にそれらがなくてもエラーは発生しないのだけれど、ほんとうにそれでいいのか? って、そう考えるとそれは「よくない」気がして・・・ならないし。

いちばん気になる、各パラメータ値の設定を記録した「セクションそのものがない」場合にはどうするか?、その対処方法を、知ってるか? って問われたら、その答えを僕はもちろん、知らない。そのことに、ここでようやく気付いた。この場合、知らないでは済ませるのは「すーぱー嫌」なので、良い機会だと思い、それを調べてみることにした。

2.セクションの存在を確認

調べてみると、やはりini ファイルにセクションが存在するか・どうかを調べるメソッドがちゃんと用意されていた。TIniFile の仕様的にエラーが発生しないから、このメソッドの必要性をこれまで僕は感じなかったんだろう・・・。

System.IniFiles.TCustomIniFile.SectionExists

https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.IniFiles.TCustomIniFile.SectionExists
SectionExists メソッドを使用すると,FileName で指定した INI ファイル内にセクションが存在するかどうかがわかります。Section は,SectionExists が存在することを決定する,INI ファイルのセクションです。SectionExists は Boolean 値を返して,対象のセクションが存在するかどうかを示します。 (上記リンク先より引用)


で、書いてみたのがこちら。

  //iniファイルから設定データを読込み
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    //Settingsセクションの有無を調査
    if Ini.SectionExists('Settings') then
    begin
      str1:=Ini.ReadString('Settings', '文字列型_閾値', '180');
      str2:=Ini.ReadString('Settings', '文字列型_平滑化強度', '41');
      str3:=Ini.ReadString('Settings', '文字列型_判定係数', '3');
      //Python39-32フォルダの有無を調査
      if System.SysUtils.DirectoryExists('Python39-32') then
      begin
        str4:=Ini.ReadString('Settings', '文字列型_手法', '正規化相互相関');
      end else begin
        str4:=Ini.ReadString('Settings', '文字列型_手法', '差分相関');
      end;
      str5:=Ini.ReadString('Settings', '文字列型_選択肢の始まり', '1');
      //str5:=Ini.ReadString('Settings', '文字列型_左補正', '');
      //str6:=Ini.ReadString('Settings', '文字列型_右補正', '');
      //str7:=Ini.ReadString('Settings', '文字列型_上下補正', '');
      str8:=Ini.ReadString('Settings', '文字列型_判定領域', '70');
      str9:=Ini.ReadString('Settings', '文字列型_読上速度', '3');
      //追加
      CheckZahyo.Checked:=
        Ini.ReadBool('Settings', '論理値型_座標チェック', True);
      CheckVolMixer.Checked:=
        Ini.ReadBool('Settings', '論理値型_音量ミキサー表示', False);
      boolSpeed:=Ini.ReadBool('Settings2', '文字列型_速度', False);
    end else begin
      //デフォルトのパラメータを表示
      str1:='180';
      str2:='41';
      str3:='3';
      //Python39-32フォルダの有無を調査
      if System.SysUtils.DirectoryExists('Python39-32') then
      begin
        str4:='正規化相互相関';
      end else begin
        str4:='差分相関';
      end;
      str5:='1';
      str8:='70';
      str9:='3';
      //追加
      CheckZahyo.Checked:=True;
      CheckVolMixer.Checked:=False;
      boolSpeed:=False;
      //設定を保存するようユーザーに案内するためのフラグを設定
      P4D_Exist:=False;
    end;
  finally
    Ini.Free;
  end;

プログラムが冗長化しただけ・・・のような気もするし、「セクションがない」場合には勝手にそれを作成して保存してしまう手もあるんだけど・・・。デフォルト・パラメータはあくまでも僕の環境(マークシートの紙質、画像化する際に利用するスキャナー、読み取り解像度、その他いろいろ)でのベストな値であって、環境が異なれば当然調整しなければならない場合もあり得る。そう考えると、もし、ini ファイル内にセクションそのものが存在しなかった場合は、それをユーザーに案内してきちんとセクションとして保存してもらえる仕様にするのがいちばんいいと思えてきた。

そこで、上記のように、ユーザーへの案内(通知)の要/不要を判断するフラグを用意。さらに、ユーザーへの案内(通知)は、Formが表示されてからの方が、なんとなく安心感があるかな? って思ったので、もし、セクションがなかった場合は、Form が表示された直後に案内(通知)するようにプログラミング。

private
    { Private 宣言 }
    //Python4Delphiの有無を知るフラグ
    P4D_Exist:Boolean;

procedure TFormMSReader.CMShowingChanged(var Msg: TMessage);
var
  strMsg: string;
begin
  inherited; {通常の CMShowingChagenedをまず実行}
  if Visible then
  begin
    Update; {完全に描画}
    if not P4D_Exist then
    begin
      strMsg:='読み取りパラメータの設定が、デフォルト値となっています。'+#13#10+
      '必要に応じて読み取りパラメータの調整を行い、'+
      '「設定を保存」ボタンがアクティブな状態で保存してください。';
      Application.MessageBox(PChar(strMsg), PChar('情報'), MB_ICONINFORMATION);
    end;
  end;
end;

セクションそのものがない場合には、i マークのアイコン付きでメッセージボックスを表示する。

3.まとめ

今回、初めて知った SectionExists メソッド。使わなくてもエラーは発生しないのだけれど、このメソッドについて調べたことで、あらためて「プログラムの仕様」について考える大変良い機会を得ることができました。いつか先輩に言われた言葉が胸によみがえります。

「きちんと動くプログラムが、いいプログラムなんだ。」

きちんと動く・・・って、その意味は、僕が思っていた以上に、深いようです。

4.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。