なでしこ1.5321がリリース

今日、なでしこ1.5321がリリースされました!今回のバージョンアップには、大分頑張って貢献しました。せめて長期休暇中だけでもコミットしておきたいわけですよ。

というわけで、今回のバージョンアップで関わった部分:

  • 命令の引数の型を変換する際の不具合を修正 (r189)
  • 「ディスクサイズ」「ディスク空サイズ」命令で、存在しないディスクを指定したら-1を返すように修正。 (r190)(r191)(@330)

前のバージョン(1.531)はリリースも遅めで不具合が多かったため、今回のバージョンアップまでのスパンがなんだか短かったですね。それで、本当はマニュアル探査艦周りも修正したかったのですが、暇がありませんでしたf(^^; まぁ、ぼちぼちやっていきます。

検索 〜ソースコードを追え!〜

今回は、「ディスクサイズ」命令の修正を具体例として、 Turbo Delphi でプログラムのソースコードを追う方法を見ていきます。

命令の検索

まず、なでしこプロジェクト(EXEGroup.bdsgroup)を Turbo Delphi で開いたら、今回修正したい「ディスクサイズ」命令が定義されている場所を見つけましょう。ファイルを横断検索するには、メニューから「検索 - ファイル検索」もしくは「Shift + Ctrl + F」です。

ファイル検索画面にディクサイズと入力する

「ディスクサイズ」と入力して検索すると、下のペインに検索結果が一覧表示されます。ペインというのは、こういった情報をまとめて表示するひとまとまりのエリアのことです。設定によっては右下など別の場所に表示されるかも知れません。検索結果は、こんな感じでツリー表示されます:

[-]  C:\svn\nadesiko\hi_unit\dll_file_function.pas
       dll_file_function.pas(2969):   AddFunc  ('ディスクサイズ','{=?}Aの',648, sys_getDiskSize,'ディスクAの全体のバイト数を返す。','でぃすくさいず');

AddFunc

AddFunc という命令を書いた行がヒットしました。

AddFunc  ('ディスクサイズ','{=?}Aの',648, sys_getDiskSize,'ディスクAの全体のバイト数を返す。','でぃすくさいず');

この AddFunc というのが、なでしこのシステム命令を登録する重要な命令です。第1引数から順番に、なでしこにおける命令名、なでしこでの引数の与え方(助詞など)の定義、命令ID、実際に Delphi 側が実行するハンドラ、命令の簡単な説明、命令の読み、となっています。

では、なでしこの「ディスクサイズ」命令を実行した時に実際に呼び出される処理 sys_getDiskSize (第4引数) はどこで定義されているのでしょうか。今度はこの sys_getDiskSize で検索をかけると、次のような結果になります:

[-]  C:\svn\nadesiko\hi_unit\dll_file_function.pas
       dll_file_function.pas(2045): function sys_getDiskSize(args: DWORD): PHiValue; stdcall;
       dll_file_function.pas(2969):   AddFunc  ('ディスクサイズ','{=?}Aの',648, sys_getDiskSize,'ディスクAの全体のバイト数を返す。','でぃすくさいず');

Delphi の関数定義の書式

最初の検索で出てきた AddFunc の他に、 function sys_getDiskSize(args: DWORD): PHiValue; stdcall; という sys_getDiskSize の関数定義らしき行が見つかりました。

function sys_getDiskSize(args: DWORD): PHiValue; stdcall;

検索結果ペインでダブルクリックすれば、ヒットしたファイルのその行に飛ぶことができます。

function sys_getDiskSize(args: DWORD): PHiValue; stdcall;
var
  p: PHiValue; sp: string;
  iFree, iTotal: TLargeInteger;
begin
  p := nako_getFuncArg(args, 0);
  if p = nil then p := nako_getSore;
  sp := hi_str(p);

  GetDiskFreeSpaceEx(PChar(sp), iFree, iTotal, nil);
  Result := hi_newFloat(iTotal);
end;

慣れていないと分かりづらいので、 Delphi の関数定義について説明しておきます。基本的な書式は次の通りです:

function 関数名 (引数1: 型1, 引数2: , …): 返り値の型;
var
  ローカル変数A: 型A;
  ローカル変数B: 型B;
  …
begin
  処理
  …
  Result := 結果
end;

特徴的なのは、ローカル変数をメイン部分の前で前方宣言していることと、返り値を Result という特別な変数に代入する点でしょう。

sys_getDiskSize

前置きが長くなりましたが、ディスクサイズを取得する処理 sys_getDiskSize の内容を追っていきます。

p := nako_getFuncArg(args, 0);
なでしこ側で指定した引数(0番目)を取得する。
if p = nil then p := nako_getSore;
なでしこ側で引数の省略が行われていた場合、「それ補完」を行う。
sp := hi_str(p);
なでしこの文字列から、 Delphi の標準の文字列型に変換する。
GetDiskFreeSpaceEx(PChar(sp), iFree, iTotal, nil);
システム命令で空き容量・総容量を調べて iFree, iTotal にセットする。
Result := hi_newFloat(iTotal);
なでしこの数値型に直して結果を返す。

修正 〜ソースコードを捕まえろ!〜

これで、「ディスクサイズ」命令の内容は分かりました。ところが、肝心のディスクサイズ取得はシステム命令で行っているようなので、変更のしようがありません。ということで、ソースコードの追跡はこの辺で諦めて、修正ステップに移りましょう。

今回修正したいのは、存在しないディスクを指定したときに取得される値が変になる現象です。これでは困るので、代わりに-1を返すようにするのが目的です。

ということで、後はディスクが存在するかチェックして条件分岐するだけです。…が、ディスクの存在をチェックする命令なんて知らないので、今回はここでまた検索を駆使しました。なでしこの「フォルダ存在」命令を調べれば、 Delphi でフォルダの存在を判定する命令が何か分かりますね。

同じようなステップで検索すればすぐに分かるので、読者の宿題ということにして結果だけ書くと、 DirectoryExists 命令を使えばいいことが分かりました。修正したソースコードは以下:

  if DirectoryExists(sp) then
  begin
    GetDiskFreeSpaceEx(PChar(sp), iFree, iTotal, nil);
  end else
  begin
    iTotal := -1;
  end;

修正完了!です。セットで修正した「ディスク空きサイズ」命令も、すぐお隣りにあるので同様の修正ですぐ終わりました。実は今回の修正だけでも、なんと合計5回以上の検索を駆使しています。

このように、巨大なプログラムを扱う上では、検索能力が必須と言っても過言ではありません。検索してコードを読んでまた検索するヒットアンドアウェイが基本戦法というわけです。

関連・参考リンク

記事を書くにあたって Delphi の資料を探していたら、なんと昔クジラさんが書いた資料に行きあたりました!内容的には多少古いようですが、今まで参考にしていた他の資料よりも細かくかつ分かりやすく解説してくれていたので非常に助かりました。