./index.html ../index.html

デバッグ三題+2

僕がデバッグ用に書いたユニット群。 ほとんどの方々は僕程度の書くユニットなど不要なはずなので恐縮ですが、万が一入り用でしたらお持ち下さい。

Memory Leak

SystemユニットのAllocMemCountには、Borlandのメモリーマネージャが管理している、確保されて、解放されていないメモリーの数が入っています。 この値を最初に覚えておいて、終了時に比較することで、解放漏れを見つけることができます。 上のアーカイブでは、Debug_MemCheck.pasが、このチェックを実装したユニットです。

気をつけることは、チェックコードそのものが、メモリーを確保してしまわないようにすること、GlobalAllocやSysAllocStringみたいな、メモリーマネージャの管理外のメモリーはリークしていても発見できないことでしょうか。

例外事項として、メインプログラム中で宣言された自動管理の変数(AnsiString他)は、先頭でusesされたユニットのfinalization(つまりユーザーが普通に書ける中で一番最後に実行される場所)よりも後で破棄されますので、誤報の元になります。このチェックを行っているときは、メインプログラムはいくつかのルーチンを呼び出すに留めておいたほうが賢明です。

OutputDebugString

DirectXなんか使っていると、デバッグ出力を画面以外に出す必用に迫られます。 しかし、ファイルにログを残す方法は、いちいちファイルを開かないといけないし、速度にもHDDにもダメージが行くし、あまり使いたくありません。

そんな時、OutputDebugStringは、非常に便利なデバッグの手段です。 [Ctrl]+[Alt]+[V]でぱっと出てくれるし。 デバッグオプションで、OutputDebugString以外のデバッグ出力は全て切っておきましょう。 で、Debug_Output.pasを、どこかでusesしておきます。 すると不思議、GUIモードアプリケーションから Write('ABC') とできて、その出力はイベントログに書き出されます。 コンソールでも、Text型(System.TextまたはSystem.TextFile)の変数を宣言して、AssignDebugするだけ。 テキストファイルドライバの例としてもご覧ください。 ああ、宣伝文句になっちまった。

2002-09-25 Text型のローカル変数を使っても大丈夫なように…って今までがバグありってこと(汗

Unit Test

eXtreme Programmingという開発手法があって、理念はともかくその中のUnit Testのフレームワークは、小規模ながら複雑なルーチンのデバッグにそこそこ便利です。 (暴言だな…)

例えば、ワイルドカード検索をFindFirstFileに頼らず独自に実装するとか、正規表現をJScript用にCOMオブジェクトとしてWindowsにあるものを使わずに独自に実装するとか、JIS/SJIS/EUCのコード変換関数を作るとか(我ながら実に無駄なことばかりしている気がする)、過去のテストを残したまま新しいテストを追加して一括実行できます。

DelphiのUnit Test フレームワークは、本家(?)と、日本人の手によるものとがあります。どちらもDUnitを名乗っています。

でも、僕としては派手なGUIで見せてくれるよりもコードエディタとデバッグログはDelphi環境内同士ということもありOutputDebugStringでデバッグログに出してくれた方が便利なのでそーいうのを自分で書いちゃったりしてます。それがDebug_UnitTest.pasです。 (実はDUnitを見つけたのがDebug_UnitTest.pasを書いた後だったりして)

+1 例外でデバッガを停止しない

例外を流れ制御として普通に使おうと思えば、いちいちデバッガが例外で止まってくれるのが鬱陶しくなってきます。 デバッグオプションで種類別に止まらないようにさせることはできますが、それよりも、場所別で止まらないようにさせることができたら便利です。 デバッグが終わったと断言できる箇所ではそのまま実行し、それ以外の箇所では止まって欲しいのです。
ブレークポイントの設定の中に、以降例外で止まらないようにするような機能があるのですが、これはネストが効かない上デスクトップの保存をOFFにしていると保存もされないので不便です。
IDEは、System.ExceptionClassに代入されているクラスの派生クラスのみデバッガで取り扱うので、これを、そこでは投げられない例外、または、元々止まらない設定のEAbortにしてやることで、目的の動作を達成できます。 nilでも良さそうな気がしますが、逆に全ての例外で止まる様になります。

program ExceptionSample;
{$APPTYPE CONSOLE}
uses 
  SysUtils;
var
  O: TClass;
begin
  O := ExceptionClass;
  ExceptionClass := EAbort;
  try
    try
      raise Exception.Create('msg')
    except
    on E: Exception do
      WriteLn(E.Message)
    end;
    try
      Abort
    except
    on E: Exception do
      WriteLn(E.Message)
    end
  finally
    ExceptionClass := O
  end
end.

+2 コンソールアプリケーション

DelphiのIDEがVisual Studioに劣る面として…いや、これが実は多々有りますが*1…まあとにかくその内のひとつとして、割と致命的な事に、Delphiでコンソールアプリを書いていて、それをデバッガから実行すると、終了時に、待ってくれずいきなり終了してしまいます。
それを防ぐためにReadLnを入れている方も多いと思われますが、普通にReadLnと書いただけでは、コンソールアプリ本来の目的であるコマンドプロンプトからの実行時も、律義に待ってくれて迷惑です。
条件コンパイルやコマンドラインパラメータで切り分ければいいのですが、それも割と面倒です。

てなわけで、if DebugHook <> 0 then ReadLnという書き方を覚えておけばよいと思われます。
デバッガで実行している時のみReadLnが実行されます。

例えばcrt.pas


*1 デフォルトがRAD前提で、純粋にコードを書きたい時各種窓を並べ替えるのが面倒とか。 スマートインデントしてくれないとか。 Visual Studioは入力支援のヒントで直後のコメントも表示してくれるらしいとか。 デバッガで、Visual Studioはソースとアセンブリの混在モードが便利とか、対するDelphiのCPUウィンドウは別窓なのでフォーカス移動が割と手間だとか。 DelphiでF1押した時に表示されるWindows APIリファレンスは未だに*.hlpの古い奴だとか。 その他諸々。 逆に勝っている面で現在確認されているのは、Visual Studioで入力支援が壊れると、一々キャッシュファイルを消さないと直らない事。Delphiなら再起動だけでOK。(くだらねー)