2

現在、既存の 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(???)
4

4 に答える 4

7

各オブジェクトのインスタンスへのポインターはSystem.Monitor、すべてのデータ フィールドの後に格納されます。オブジェクトの最後のフィールドに大量のデータを書き込むと、モニターのアドレスに偽の値が書き込まれる可能性があり、オブジェクトのデストラクタが偽のモニターを破棄しようとすると、おそらくクラッシュにつながります。このアドレスがフォームnilBeforeDestructionメソッドにあることを確認できます.Delphi 5のストレートポートには、モニターが割り当てられていないはずです。何かのようなもの

procedure TForm1.BeforeDestruction;
var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  Assert(MonitorPtr^ = nil);
  inherited;
end;

これが元のコードの問題である場合は、FastMM4 メモリ マネージャーを使用してすべてのチェックを有効にすることにより、Delphi 5 バージョンの DLL で問題を検出できるはずです。OTOH これは、Unicode ビルドでの文字データのサイズの増加によっても発生する可能性があり、その場合、Delphi 2009 または 2010 を使用した DLL ビルドでのみ発生します。すべてのチェックで最新の 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] をクリックします。デバッガーが起動し、次のように、そのポインターへの書き込みアクセスにブレークポイントを設定します。

バ w4 A32D00

A32D00メッセージボックスから正しいアドレスに置き換えます。実行を続行すると、モニターが割り当てられたときにデバッガーがブレークポイントにヒットするはずです。さまざまなデバッガー ビュー (モジュール、スレッド、スタック) を使用すると、そのアドレスに書き込むコードに関する重要な情報を取得できます。

于 2010-01-14T15:22:45.323 に答える
3

無効なポインター操作は、プログラムがポインターを解放しようとしたが、次の 3 つの問題のいずれかがあったことを意味します。

  • 他のメモリ マネージャによって割り当てられました。
  • 以前に一度解放されていました。
  • それは何によっても割り当てられたことはありませんでした。

複数のメモリ マネージャーがレコードを割り当てる可能性は低いTMonitorので、最初の可能性を除外できると思います。

2 番目の可能性として、カスタム デストラクタを持たないか、デストラクタでメモリを解放しないクラスがプログラムにある場合、そのオブジェクトの最初の実際のメモリ解放は TObject で行われる可能性があります。オブジェクトのモニターを解放します。そのクラスのインスタンスがあり、それを 2 回解放しようとすると、その問題が TMonitor で例外の形で現れる可能性があります。プログラムでダブル フリー エラーを探します。これには、FastMMのデバッグ オプションが役立ちます。また、その例外が発生した場合は、コール スタックを使用して、TMonitor のデストラクタに到達した方法を確認してください。

3 番目の可能性が原因である場合は、メモリが破損しています。オブジェクトのサイズを推測するコードがある場合は、それが原因である可能性があります。InstanceSizeTObject は、Delphi 2009 の時点で 4 バイト大きくなっています。オブジェクトのサイズを取得するには、常にメソッドを使用してください。すべてのフィールドのサイズを合計したり、マジック ナンバーを使用したりしないでください。

あなたは、スレッドが Outlook によって作成されたと言います。IsMultithreadグローバル変数を設定しましたか? プログラムは通常、スレッドを作成するときに True に設定しますが、スレッドを作成していない場合は、デフォルトの False 値のままになります。これは、メモリ マネージャーが割り当てと割り当て解除中にグローバル データ構造を保護するかどうかに影響します。 . DPR ファイルのメイン プログラム ブロックで True に設定します。

于 2010-01-14T15:18:38.247 に答える
1

たくさん掘り下げた後、私はいいことをしていたことがわかりました(読んでください:恐ろしいですが、私たちのデルファイ5アプリでは何年にもわたって適切に機能しています

PClass(TForm)^ := TMyOwnClass 

アプリケーション フレームワークの腸の奥深くのどこかにあります。どうやら、Delphi 2010 には「モニター フィールド」を初期化するクラスの初期化があり、現在は発生していません。これにより、getFieldAddress が nil 以外の値を返したため、フォームの破棄時に RTL が「syncobject を解放」しようとしました。うーん。

最初にこのハックを行った理由は、アイコンのないサイズ変更可能なフォームを実現するために、すべてのフォーム インスタンスの createParams を自動的に変更したかったからです。rtl を破るハックなしでこれを行う方法について、新しい質問を開きます (今のところ、フォームに素敵な光沢のあるアイコンを追加するだけです)。

Mghie の提案を回答としてマークします。これは、私 (およびこのスレッドを読んでいる人) に非常に多くの洞察を提供してくれたからです。貢献してくれてありがとう!

于 2010-01-18T18:37:17.947 に答える
0

Delphi には 2 つの TMonitor があります。

  1. System.TMonitor; これはレコードであり、スレッドの同期に使用されます。
  2. Forms.TMonitor; これは、接続されたモニター (ディスプレイ デバイス) を表すクラスです。

System.TMonitor は、Delphi 2009 以降、Delphi に追加されました。そのため、Delphi 5 からコードを移植する場合、コードで使用していたのは System.TMonitor ではなく Forms.TMonitor でした。

コード内でユニット名なしでクラス名が参照されていると思いますが、それが混乱を招いています。

于 2010-01-14T14:00:01.483 に答える