TMonitor.Destroy での無効なポインター操作
-
20-09-2019 - |
質問
現在、既存の Delphi 5 アプリケーションを Delphi 2010 に移植する作業を行っています。
これは、Outlook に読み込まれるマルチスレッド DLL (スレッドは Outlook によって生成される) です。Delphi 2010 でコンパイルすると、フォームを閉じるたびに TMonitor.Destroy 内で「無効なポインタ操作」が発生します。つまり、system.pas にあるものです。
これは既存のちょっと複雑なアプリケーションなので、 多く 調べるべき方向とデルファイのヘルプ 文書化すらしない そもそも、この特定の TMonitor クラスについてはほとんど文書化されていません (追加情報を含む Allen Bauer の投稿をいくつか追跡しました) ...そこで、まず誰かがこれまでにこの問題に遭遇したかどうか、またはこの問題の原因について何か提案があるかどうかを尋ねてみようと思いました。記録のために:コード内で TMonitor 機能を明示的に使用していません。ここでは Delphi 5 コードの直接移植について話しています。
編集 問題が発生した時点のコールスタック:
System.TMonitor.Destroy
System.TObject.Free
Forms.TCustomForm.CMRelease(???)
Controls.TControl.WndProc(???)
Controls.TWinControl.WndProc((45089, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Forms.TCustomForm.WndProc(???)
Controls.TWinControl.MainWndProc(???)
Classes.StdWndProc(15992630,45089,0,0)
Forms.TApplication.ProcessMessage(???)
解決
各オブジェクトのSystem.Monitor
インスタンスへのポインタは、すべてのデータフィールドの後に格納されます。あなたがオブジェクトの最後のフィールドにあまりにも多くのデータを記述する場合、あなたがオブジェクトのデストラクタが偽のモニターを破壊しようとしたとき、おそらくクラッシュにつながるモニター、のアドレスに偽の値を書き込むことを発生する可能性があります。あなたはストレートデルファイ5ポートに割り当てられて任意のモニターがあってはならない、フォームのnil
方法でこのアドレスであることBeforeDestruction
をチェックすることができます。
procedure TForm1.BeforeDestruction;
var
MonitorPtr: PPMonitor;
begin
MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
Assert(MonitorPtr^ = nil);
inherited;
end;
これは、あなたが起動し、すべてのチェックをFastMM4メモリマネージャを使用して、DLLのデルファイ5バージョンでそれを検出することができるはず、あなたの元のコードで問題となっている場合。 Unicodeのビルドで大藤これはまた、文字データのサイズの増加が原因である可能性があり、その場合には、DLLの唯一のマニフェストのDelphi 2009または2010を使用して構築することは、まだすべてのチェックで、最新のFastMM4を使用することが良いでしょうでしょう。
の編集の
モニターが実際に割り当てられているように、あなたのスタックトレースから、それが見えます。私はデータブレークポイントを使用する理由を見つけるために。私は、彼らは、Delphi 2009で動作させることができていないが、あなたはWinDbgので簡単にそれを行うことができます。
フォームのOnCreate
ハンドラでは、次のように入れます:
var
MonitorPtr: PPMonitor;
begin
MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
MessageDlg(Format('MonitorPtr: %p', [pointer(MonitorPtr)]), mtInformation,
[mbOK], 0);
DebugBreak;
// ...
今のWinDbgとオープンをロードし、あなたのDLLを呼び出すプロセスを実行します。フォームが作成されると、メッセージボックスを使用すると、モニター・インスタンスのアドレスが表示されます。アドレスを書き留めて、[OK]をクリックします。デバッガが出てくるだろう、とあなたはそのポインタへの書き込みアクセスにブレークポイントを設定し、そのようなます:
BAのW4のA32D00
のメッセージボックスから正しいアドレスとA32D00
を置き換えます。実行を継続し、モニタが割り当てられますとき、デバッガはブレークポイントにヒットする必要があります。様々なデバッガビュー(モジュール、スレッド、スタック)を使用して、あなたはそのアドレスに書き込むコードについての重要な情報を得ることができます。
他のヒント
無効なポインター操作は、プログラムがポインターを解放しようとしたが、次の 3 つのうちのいずれかが間違っていたことを意味します。
- 他のメモリ マネージャーによって割り当てられました。
- 以前に一度解放されていました。
- それは何によって割り当てられたこともありませんでした。
複数のメモリ マネージャーが割り当てられる可能性は低いです。 TMonitor
記録があるので、最初の可能性は排除できると思います。
2 番目の可能性については、カスタム デストラクターを持たないクラス、またはデストラクター内のメモリを解放しないクラスがプログラム内にある場合、そのオブジェクトの最初の実際のメモリ割り当て解除は TObject 内で行われる可能性があります。オブジェクトのモニターを解放します。そのクラスのインスタンスがあり、それを 2 回解放しようとすると、その問題が TMonitor で例外の形で発生する可能性があります。プログラム内のダブルフリー エラーを探します。の FastMM のデバッグ オプション それを助けることができます。また、その例外が発生した場合は、 コールスタック TMonitor のデストラクターにどのようにアクセスしたかを確認してください。
3 番目の可能性が原因の場合は、メモリ破損が考えられます。オブジェクトのサイズを仮定するコードがある場合、それが原因である可能性があります。Delphi 2009 の時点では、TObject は 4 バイト大きくなっています。常に使用してください InstanceSize
オブジェクトのサイズを取得するメソッド。すべてのフィールドのサイズを単純に合計したり、魔法の数値を使用したりしないでください。
スレッドは Outlook によって作成されると言います。設定しましたか IsMultithread
グローバル変数?通常、プログラムはスレッドを作成するときにこの値を True に設定しますが、スレッドを作成しているのが自分ではない場合は、デフォルトの False 値のままになります。これは、メモリ マネージャーが割り当ておよび割り当て解除中にグローバル データ構造をわざわざ保護するかどうかに影響します。 。DPR ファイルのメイン プログラム ブロックでこれを True に設定します。
それを掘るの多くは、私は(読み:、恐ろしい<全角>が、それは適切に年齢のために私たちのデルファイ5つのアプリケーションでその仕事をしてきたの)素敵なをしていたが判明した後、
PClass(TForm)^ := TMyOwnClass
どこか深いところで私たちのアプリケーションフレームワークの腸インチどうやらデルファイ2010は、今RTLがしようとするとgetFieldAddressが非nilの値を返したため、フォームの破壊時「syncobjectを解放」させ、発生しませんでした「モニターフィールド」を初期化するために、いくつかのクラスの初期化されています。うわます。
理由の理由の我々は最初の場所でこのハックをやっていた私は、自動的にすべてのフォームのインスタンスにCreateParamsをを変更するようだったので、iconlessサイズ変更可能なフォームを達成するために、でした。私は、RTL-破壊ハックせずにこれを行う方法の新しい質問を開きます(そして今は単にフォームに素敵な光沢のあるアイコンを追加します)。
それは洞察力の非常に大きな量で私(そして、このスレッドを読んで誰を)提供しているので、私は、答えとしてMghieの提案をマークします。貢献のためのみんなありがとう!
Delphi には 2 つの TMonitor があります。
- System.TMonitor;これはレコードであり、スレッドの同期に使用されます。
- Forms.TMonitor;これは、接続されたモニター (表示デバイス) を表すクラスです。
System.TMonitor は、Delphi 2009 以降、Delphi に追加されました。したがって、Delphi 5 からコードを移植している場合、コードで使用されていたのは System.TMonitor ではなく Forms.TMonitor でした。
コード内でユニット名なしでクラス名が参照されているため、混乱が生じていると思います。