5

静的にリンクされたランタイムパッケージと、それらを使用するデザインタイムパッケージを使用するアプリケーションがあります。何らかの理由で、ユニットファイナライズセクションのコードが実行時に実行されていません(これがいつ発生し始めたかはわかりません)。

finalization
  ShowMessage('Goodbye');
end.

Delphiをシャットダウンするとメッセージが表示されますが、アプリケーションがシャットダウンしたときは表示されません。ShowMessageにブレークポイントを設定すると、そこでブレークしますが、行は実行されないという点で、さらに奇妙になります。ファイナライズに複数の行がある場合、デバッガーは最初の行で停止し、実行せずに最後にジャンプします。

procedure ProcOne;
begin
  SomeObject.Free; // Debugger does not enter or stop here
  SomeObject := nil;
end;

finalization
  ProcOne; // Debugger stops here, doesn't execute, jumps to "end."
  ProcTwo; // Every line has a blue dot
  ShowMessage('Bye');
end.

ProcOneブレークポイントのコールスタックは、@ Halt0 => FinalizeUnits=>MyPackage.MyUnit.Finalizationを示しています。

パッケージを使用しないアプリケーションにユニットを含めると、すべてが正しく実行されます。

誰かがこれを引き起こしている可能性がある考えを持っていますか?

編集:

正しい方向を指しているアレンバウアーのコメントのおかげで、私は問題を切り分けることができました。アプリケーションがランタイムパッケージでビルドされ、そのパッケージとユニットを参照する別のパッケージを動的にロードすると、問題が発生するようです。

問題を実証するテストプロジェクトを作成しました:TestFinalization

誰かがこれの理由および/または回避策を知っていますか?通常、外部リソースがクリーンアップされていないことに気付くまで、ファイナライズが実行されていないことに気付かない場合があります。

4

2 に答える 2

10

シャットダウンする前に、動的にロードされたパッケージごとにUnloadPackageを呼び出していることを確認してください。UnloadLibraryを呼び出すだけの場合(または単にOSに依存してアンロードする場合)、そのパッケージ内のユニットと他のパッケージのすべてのユニットのファイナライズは呼び出されません。初期化と最終化は、参照カウントシステムを使用して行われます。これは、動的にロードされたパッケージに直面すると、どのユニットがいつ初期化されるかを知る方法がないためです。ファイナライズ呼び出しと初期化呼び出しのバランスをとった場合にのみ、最後のファイナライズ呼び出しが実際にファイナライズセクションのコードブロックを実行します。同様に、初期化セクションへの最初の呼び出しのみが実際にコードブロックを実行します。

初期化/最終化は、特定のモジュール用にコンパイラーが生成したテーブルを使用して行われます。パッケージにリンクされたexeまたはdllをビルドする場合、このテーブルには、リンクされたパッケージからのものも含め、実際に使用されるすべてのユニットへの参照が含まれます。実際に参照されるユニットのみが実際に初期化されることに注意してください。IOW、PackageAに100ユニットがあり、exeがそのうちの1つのみを参照している場合、そのユニットとそれが使用するユニットのみが初期化されます。

動的にロードされたパッケージの場合、実際にどのユニットが使用されるかを知る方法は実際にはないため、コンパイラーは、すべてのユニットが初期化されたかのようにinit/finitテーブルを生成します。このテーブルは、LoadLibraryの呼び出し中にパッケージをロードするときに処理されませんが、Initialize()と呼ばれる特別なエクスポートを呼び出すことによって処理されます。LoadPackage関数は、この関数が呼び出されることを保証します。このテーブルは、ロードパッケージ内のすべてのユニットが初期化されることを保証するだけです。上記のexe/dllの場合と同様に、他のパッケージで実際にアクセスされたユニットのみが初期化されます。UnloadPackgeはその逆を行い、UnloadLibrary()を呼び出す前に特別なエクスポートFinalize()を呼び出します。

最後に、パッケージ化されたユニットの使用リストに変更を加え、パッケージを再構築するだけの場合、特定のパッケージ内のユニットが互いに適切に「使用」していても、初期化/ファイナライズが呼び出されないという紛らわしいケースに遭遇する可能性があります。これは、init / finitが、それ自体の内部からではなく、ロードモジュールによって制御されるためです。LoadPackageを使用してパッケージが明示的にロードされた場合にのみ、そのパッケージ内のすべてのユニット(およびそのパッケージのみ)が初期化/終了されます。

于 2011-09-28T17:19:45.113 に答える
0

アレンバウアーの答えに照らして、私と同じ状況にある人のために:

初期化/最終化を使用して自己登録するユニットがいくつかあります。レイモンド・チェンのアドバイスの精神で、私はそれが重要な場合にのみ登録抹消を実行します。

initialization
  RegisterUnit();
finalization
//In debug mode we track memory leaks so properly deregister
//In release mode the app is shutting down; do not waste time
//freeing memory that's going to be freed anyway
{$IFDEF DEBUG}
  UnloadUnit();
{$ENDIF}

finalization私はこれらの束をパッケージに移動しましたが、質問が説明しているように、これはコアパッケージを壊しました。

Allen Bauerの回答から、動的にロードされたすべてのパッケージを呼び出す必要がありますUnloadPackage()。そうしないと、コアで適切なファイナライズ呼び出しを取得できません。

しかし、その場合、その最適化はもう使用できません。パッケージDLLをアンロードすると、コアに登録されたオブジェクトがゾンビになるため、ファイナライズ時にすべてのパッケージを苦労して登録解除する必要があります。

これは無駄な努力のように感じます。荷降ろしは迅速に行う必要があります。すべてが単純に破壊されるまで、動的にロードされたすべてのパッケージをぶら下げたままにしておきたいです。

代わりにできることはFinalizePackage()、動的にロードされたすべてのパッケージを呼び出すことです。これにより、パッケージをロードしたまま、参照カウンターが均等になります。

パッケージがこの「スキップdeinit」トリックを採用している場合、そのオブジェクトはプロセスが破壊されるまで存続します。(パッケージの仕事は、それらで呼び出すことができるものが壊れないようにすることです)。そうでない場合は、完全に非初期化され、残っているのは不活性なDLLです。

もちろん、これは本当に動的にアンロードすることを計画しているパッケージでは機能しません。

于 2019-10-08T10:33:54.430 に答える