Исключение использования освобожденных объектов в быстрых циклах рисования - PullRequest
1 голос
/ 03 марта 2011

Суммирование:
Для функции / процедуры Delphi, если экземпляр класса передается в качестве аргумента, создается другая ссылка (помимо исходной ссылки) на временный стек вызовов для указанияк этому экземпляру и используется локально.Таким образом, будьте осторожны:

(1) Если функция / процедура хочет только изменить содержимое / поля / свойства этого экземпляра, префикс var не требуется;

(2) Если функция / процедура, вероятно, хочет переназначить ссылку на новый экземпляр, используйте префикс var, или это временная ссылка, которая будет переназначена.

(3) Обратите внимание, что если функция / процедура переназначает ссылку, а префикс var не используется, результат, вероятно, будет правильным, что еще хуже, потому что когда-нибудь код сломается.

=============================================
Ситуация:
Это небольшое приложение.TMolForm является формой MDIChild, и каждый TMolForm содержит TMolScene, который происходит от TPaintBox.TMolScene рисует TMol.В процедуре рисования TMolScene TMolScene вызывает TMol.Rescale, если TMolScene изменяется.Затем TMolScene вызывает TMol.TransformCoordinates для построения координат для последующего рендеринга.

Проблема заключается в следующем:
Теперь в TMol.Rescale я сбрасываю матрицы, пропущенные вызывающей стороной TMolScene.Однако я встречаю исключения, которые не могу придумать причину.

(1) В частности, если у меня есть множественный TMolForm и быстро , делайте изменение размера, перетаскивание мышью (вращение молекулы), переключение между TMolForm, менее чем заЧерез 5 минут матрицы (предположительно уже сброшенные в TMol.Rescale) передаются в TMol.TransformCoordinates равны нулю или содержат нулевое содержимое.

(2) Если я включу FastMM4 и его FullDebugMode, и повторю вышеуказанные движения мыши, я могу получить «TMol.Rescale пытается освободить освобожденный объект».Похоже, что TMol.Rescale вызывается снова, когда последний вызов (или последний цикл рисования) не завершен.Я имею в виду, что я не делал никаких попыток, связанных с многопоточностью, как возможно, что TMol.Rescale может быть вызван во второй раз, когда последний вызов еще не возвращается?Я полностью потерян.Не могли бы вы помочь прокомментировать возможные причины?

(3) Если я удаляю сброс матриц из TMol.Rescale и в вызывающую его, TMolScene.OnScenePaint, исключения, похоже, не произойдут, по крайней мере, через 5 минут.(Я не быстро не злоупотреблял мышью дольше 5 минут. Возможно, есть другой лучший способ тестирования.) Я понятия не имею, почему это работает и почему вышеупомянутые сбои иногда .

(4) Если у меня есть только один TMolform, вышеупомянутые исключения, по-видимому, не произойдут, по крайней мере, через 5 минут.

Я должен признать, что я составил следующий свернутый код, чтобылови исключения.Однако, хотя процедура выполнения должна отражать реальную ситуацию, исключения не происходят.Если вы хотите увидеть реальный код, который я хочу отправить вам по электронной почте или другим способом.Это хобби и не хорошо написано, хотя, извините.

Любые предложения, касающиеся исключений или плохих привычек кодирования, действительно приветствуются.

        unit uMolForm;

        interface

        uses
          Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
          ExtCtrls, Dialogs;

        type
          TVec = class;
          TMat = class;
          TMol = class;
          TMolScene = class;
          TMolForm = class;

          TVec = class
          public
            X, Y, Z: Extended;
            constructor Create; overload;
            constructor Create(aX, aY, aZ: Extended); overload;
          end;

          TMat = class
          private
            FX, FY, FZ, FT: TVec;
          public
            property X: TVec read FX;
            property Y: TVec read FY;
            property Z: TVec read FZ;
            constructor Create;
            destructor Destroy; override;
            function ToUnit: TMat;
          end;

          TMol = class
          public
            constructor Create;
            destructor Destroy; override;
            procedure Rescale(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
            procedure TransformCoordinates(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
          end;

          TMolScene = class(TPaintBox)
          private
            FBbWidth, FBbHeight: Integer;
            FRotationMat, FTranslationMat, FScalingMat: TMat;
            FMol: TMol;
            procedure OnScenePaint(Sender: TObject);
            procedure OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseMove(Sender: TObject; Shift: TShiftState;
              X, Y: Integer);
          public
            constructor Create(AOwner: TComponent);
            destructor Destroy; override;
          end;

          TMolForm = class(TForm)
            procedure FormClose(Sender: TObject; var Action: TCloseAction);
            procedure FormCreate(Sender: TObject);
          private
            { Private declarations }
            FMolScene: TMolScene;
          public
            { Public declarations }
          end;

        implementation

        {$R *.dfm}

        { TVec }

        constructor TVec.Create;
        begin
          inherited;

          X := 0;
          Y := 0;
          Z := 0;
        end;

        constructor TVec.Create(aX, aY, aZ: Extended);
        begin
          inherited Create;

          X := aX;
          Y := aY;
          Z := aZ;
        end;

        { TMat }

        constructor TMat.Create;
        begin
          inherited;

          ToUnit;
        end;

        destructor TMat.Destroy;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          inherited;
        end;

        function TMat.ToUnit: TMat;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          FX := TVec.Create(1, 0, 0);
          FY := TVec.Create(0, 1, 0);
          FZ := TVec.Create(0, 0, 1);
          FT := TVec.Create;

          Result := Self;
        end;

        { TMol }

        constructor TMol.Create;
        begin
          inherited;

        end;

        destructor TMol.Destroy;
        begin

          inherited;
        end;

        procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          FreeAndNil(aRotationMatUser);
          FreeAndNil(aTranslationMatUser);
          FreeAndNil(aScalingMatUser);

          aRotationMatUser := TMat.Create;
          aTranslationMatUser := TMat.Create;
          aScalingMatUser := TMat.Create;
        end;

        procedure TMol.TransformCoordinates(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          if (aRotationMatUser.X = nil) or (aRotationMatUser.Y = nil) or
            (aRotationMatUser.Z = nil) or (aTranslationMatUser.X = nil) or
            (aTranslationMatUser.Y = nil) or (aTranslationMatUser.Z = nil) or
            (aScalingMatUser.X = nil) or (aScalingMatUser.Y = nil) or
            (aScalingMatUser.Z = nil) then
          begin
            raise Exception.Create('what happened?!');
          end;
        end;

        { TMolScene }

        constructor TMolScene.Create(AOwner: TComponent);
        begin
          inherited;

          FRotationMat := TMat.Create;
          FTranslationMat := TMat.Create;
          FScalingMat := TMat.Create;
          FMol := TMol.Create;

          Self.OnPaint := Self.OnScenePaint;
          Self.OnMouseDown := Self.OnSceneMouseDown;
          Self.OnMouseUp := Self.OnSceneMouseUp;
          Self.OnMouseMove := Self.OnSceneMouseMove;
        end;

        destructor TMolScene.Destroy;
        begin
          FreeAndNil(FRotationMat);
          FreeAndNil(FTranslationMat);
          FreeAndNil(FScalingMat);
          FreeAndNil(FMol);

          inherited;
        end;

        procedure TMolScene.OnScenePaint(Sender: TObject);
        begin
          if (FBbWidth <> Self.ClientWidth) or (FBbHeight <> Self.ClientHeight) then
          begin
            FBbWidth := Self.ClientWidth;
            FBbHeight := Self.ClientHeight;
            FMol.Rescale(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
              FScalingMat);
          end;

          FMol.TransformCoordinates(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
            FScalingMat);
        end;

        procedure TMolScene.OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseMove(Sender: TObject; Shift: TShiftState; X,
          Y: Integer);
        begin
          Self.Repaint;
        end;

        { TMolForm }

        procedure TMolForm.FormCreate(Sender: TObject);
        begin
          FMolScene := TMolScene.Create(Self);
          FMolScene.Parent := Self;
          FMolScene.Align := alClient;
        end;

        procedure TMolForm.FormClose(Sender: TObject; var Action: TCloseAction);
        begin
          Action := caFree;
        end;

        end.

1 Ответ

4 голосов
/ 03 марта 2011

Код

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
    begin

      FreeAndNil(aRotationMatUser);
      FreeAndNil(aTranslationMatUser);
      FreeAndNil(aScalingMatUser);

      aRotationMatUser := TMat.Create;
      aTranslationMatUser := TMat.Create;
      aScalingMatUser := TMat.Create;
    end;

является ошибкой.Вы должны передать aRotationMatUser, aTranslationMatUser, aScalingMatUser параметры по ссылке:

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      **var** aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);

Вы должны использовать var для передачи аргументов в вышеуказанной процедуре, потому что без него

  • FreeAndNil 'обнуляет' переменные временного стека, и это не имеет смысла;
  • конструктор вызывает присвоение значений временным переменным стека, что приводит к утечкам памяти.

Вопрос, почемуошибочный код иногда работает правильно (и, вероятно, даже не вызывает утечек памяти) - это отдельная история.


Еще одно редактирование

Как вы уже упоминалиDelphi объект является ссылкой.Поэтому вам не нужно использовать var для изменения объекта.Но ваша процедура отличается - она ​​изменяет сами ссылки, а не только данные, указанные в этих ссылках, поэтому вы должны передать эти ссылки (aRotationMatUser, aTranslationMatUser, aScalingMatUser) по ссылке.Вот почему вам нужно var.

...