4

私はクライアントと一緒に現場で働いており、複雑な問題で彼らを助けようとしています. Delphi に、内部の仕組みを調べて問題を特定するのに役立つツールまたは機能があることを願っています。

ここでは、私たちが扱っている問題の概要を説明します。これは、現在 Delphi 5 にデプロイされている商用アプリケーションです。この 1 年間で、アプリケーションは Delphi XE に移行されました。移行はほぼ完了していますが、いくつかの重大なエラーが発生しています。

アプリケーション自体は非常に大きく、数百のユニットと多くのサードパーティおよびカスタム コンポーネントが含まれています。私たちが直面している特定の状況では、メイン フォームが作成され、そのメイン フォームが表示される前にアプリケーションが終了します。その結果、ユニットがファイナライズされているため、この終了中にクラッシュが発生します。

デバッガーは、NotifyNonDelphiException によって呼び出される kernel32 の RaiseException 関数で中断しています。NotifyNonDelphiException 内からコール スタックをログに記録する非中断ブレークポイントを設定しようとしましたが、何も役に立ちません。コール スタックには、例外を処理したメソッド、つまり RtlRaiseStatus と KUserExceptionDispatcher のみが含まれます。

NotifyNonDelphiException によって処理されている元の例外をスローするコードを特定するにはどうすればよいでしょうか。


編集: これは、例外の 1 つのインスタンスに続いてキャプチャされた 2 つの画像です。1 つ目は発生した例外で、2 つ目は例外ダイアログ ボックスが閉じられた後の CPU ウィンドウを示しています。

退出時のアクセス違反

例外ダイアログ ボックスを閉じたときの CPU ウィンドウ

新しい編集:

この質問を投稿してから 1 週間以上経ちましたが、さまざまな回答に感銘を受けました。最初の質問に対するコメントのいくつかは最も価値がありましたが、回答自体のいくつかは非常に有益です。

そのクライアントへの私の訪問は終わりました。ここに投稿された回答を検討するよう依頼します。エラーの実際の原因を突き止めることはできませんでしたが、エラーの原因は明らかでした。深刻なリファクタリングを行わずに何年にもわたってユーザー インターフェイスを微調整した結果、アプリケーションのログイン プロセスが不安定になりました。ログインがユーザーによってキャンセルされたとき、メイン フォームは部分的に初期化された状態でした。このプロセスの実行が許可されなかった場合 (ユーザーがログインを中止した場合)、ファイナライズに関する非常に深刻な問題が発生していました。

同社は、将来の問題を特定するために AQTime Pro を購入しましたが、ログイン プロセスのリファクタリングが必要であり、長期的には問題を解決します。

ある時点でこの質問を削除することを検討しましたが、他の人が投稿された多くの優れた提案が参考になると信じているため、投稿し続けることにしました.

質問に答えがないまま放置するのは嫌なので、当面は@Delticsの回答を受け付けます。ただし、この質問の視聴者には、他のすべての回答とコメントも考慮するようお願いしています。それらは等しく価値があります。

4

3 に答える 3

5

まさにこの理由から、例外はファイナライズ(または初期化) セクションから「エスケープ」することはできません。

非常に少数の例外を除いて [sic]、ファイナライズセクションのコードはすべてtry..exceptで囲む必要があります。例外が発生したときに何をするかはあなた次第ですが、少なくともOutputDebugString()を呼び出すと、デバッグ時に情報が得られ、実際の例外が発生したときにのみブレークを引き起こすブレークポイントを設定するポイントが得られます例外が発生しました。

finalization
  try
    // Perform finalization processing here

  except
    on e: Exception do
      OutputDebugString('%s: $s in unit %s', [e.ClassName, e.Message, 'MyUnitName']);
  end;
end.

注:このコードのOutputDebugString()呼び出しは、引数を受け入れるように拡張された、 Windowsユニットの関数のラッパーである、私自身の「文字列に優しい」ものです。

おそらくファイナライズセクションにそのような例外処理がないため、先に進む前にそれらを配置する必要があります。ただし、この演習により、コード全体の品質が向上し、将来の同様の問題の診断がはるかに簡単になります (現在の例外を特定して修正すると、他のファイナライズ例外は発生しなくなります)頭?)。

また、この例外処理を適用するプロセスでは、できるだけ多くのファイナライズセクションを排除するために、各ファイナライズセクションを確認し、別の方法で処理できないかどうかを判断する機会が得られます。

これは、「不必要なオーバーヘッド」や「時間の無駄」と見なすべきではありませんが、コードの品質を許容できる水準まで引き上げるための不可欠なハウスキーピングです。

別のアプローチ

別のアプローチは、ファイナライズ セクションが導入される前に私たちがしなければならなかったように、独自のファイナライズ手順のリストを管理することです。つまり、現在ファイナライズされているユニットの初期化セクションで、現在のファイナライズ コードをパラメーターなしのプロシージャに削除し、そのプロシージャを「ファイナライズ マネージャー」に登録します。

次に、アプリケーションで、アプリケーションのシャットダウン中の適切なタイミングで「ファイナライズ マネージャー」を呼び出して、実際のユニットのファイナライズが行われる前にファイナライズを実行します。これにより、実行時例外ハンドラが配置されたまま、ファイナライズ手順が確実に実行されます。

これはまた、ファイナライズ手順が特定の決定された順序で実行されることを保証するメカニズムを備えた、より洗練された「ファイナライズマネージャー」を提供する機会を提供します (必要な場合)。この機能は、当然、独自のファイナライズ マネージャーをどのように実装するかに依存します。

于 2012-03-06T21:37:02.607 に答える
2
  1. View/Debug Windows/Modules に移動し、cxLibraryD15.bpl を見つけて、そのベース アドレスを抽出します。ここで、$00E51B9E - ベース = オフセットを減算します。

  2. アプリケーションを実行し、すぐに一時停止します。View/Debug Windows/Modules に移動し、cxLibraryD15.bpl を見つけて、そのベース アドレスを抽出します (同じである可能性があります)。ここで、ステップ 1 のオフセットを追加します: ベース + オフセット = 絶対アドレス。

  3. ブレークポイント ウィンドウまたは CPU ビューを開き、手順 2 のアドレスにブレークポイントを設定します。これで、例外が発生する直前に停止するため、デバッガーでコール スタックを確認して状況を分析できます。

于 2012-03-08T04:50:09.273 に答える
0

KiUserExceptionDispatcher、RaiseException、および Exception.GetExceptionStackInfoProc にブレークポイントを設定してみてください。

于 2012-03-07T08:47:16.347 に答える