Delphi 在快速绘制循环中使用释放对象的例外情况
总结: 对于Delphi函数/过程,如果类的实例作为参数传递,则在临时调用堆栈上创建除原始引用之外的另一个引用,以指向该实例,并在本地使用。因此,请注意: 1如果函数/过程只想更改该实例的内容/字段/属性,则不需要var前缀 2如果函数/过程可能希望将引用重新分配给新实例,请使用var前缀,或者重新分配的是临时引用 3注意,如果函数/过程重新分配了引用,并且没有使用var前缀,那么结果可能是正确的,更糟糕的是,因为代码终有一天会中断 ======================================= 情况是: 这是一个小应用程序。TMolForm是一个MDIChild表单,每个TMolForm都包含一个从TPaintBox派生的TMolScene。TMol场景绘制TMol。在TMolScene的绘制过程中,如果调整了TMolScene的大小,TMolScene将调用TMol.Rescale。然后,TMolScene调用TMol.TransformCoordinates为后续渲染建立坐标 问题是: 现在,在TMol.Rescale中,我重置了调用程序TMolScene传递的矩阵。然而,我遇到了一些例外,我想不出原因 1特别是,如果我有多个TMolForm,并快速调整大小,鼠标拖动(分子旋转),在TMolForm之间切换,在不到5分钟的时间内,矩阵应该已经在TMol中重置。传递到TMol的重缩放。Transform坐标为零或包含零内容 2如果启用FastMM4及其FullDebugMode,并重复上述鼠标移动,则可以获取TMol.Rescale尝试释放释放的对象。当最后一次调用或最后一个绘制周期未完成时,似乎会再次调用TMol.Rescale。我的意思是,我没有做任何涉及多线程的尝试,当最后一次调用还没有返回时,TMol.Rescale怎么可能被第二次调用? 我完全迷路了。你能帮忙评论一下可能的原因吗 3如果我将矩阵的重置从TMol.Rescale中移除并放入调用程序TMolScene.OnScenePaint,那么异常似乎不会发生,至少不会在5分钟内发生。我没有快速虐待老鼠超过5分钟。也许还有其他更好的测试方法。我不知道为什么会这样,为什么上面提到的有时会崩溃 4如果我只有一个TMolform,上述异常似乎不会发生,至少不会在5分钟内发生 我必须承认,为了捕捉异常,我编写了以下最小化代码。然而,尽管执行过程应该反映真实情况,但不会发生异常。如果你想看到真正的代码,我愿意通过电子邮件或其他方式发送给你。这是业余爱好,写得不好,抱歉 我们非常感谢您对异常或不良编码习惯的任何建议Delphi 在快速绘制循环中使用释放对象的例外情况,delphi,exception,free,paint,Delphi,Exception,Free,Paint,总结: 对于Delphi函数/过程,如果类的实例作为参数传递,则在临时调用堆栈上创建除原始引用之外的另一个引用,以指向该实例,并在本地使用。因此,请注意: 1如果函数/过程只想更改该实例的内容/字段/属性,则不需要var前缀 2如果函数/过程可能希望将引用重新分配给新实例,请使用var前缀,或者重新分配的是临时引用 3注意,如果函数/过程重新分配了引用,并且没有使用var前缀,那么结果可能是正确的,更糟糕的是,因为代码终有一天会中断 ===============================
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.
代码
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;
这是一个错误。您应该通过引用传递ArotationStatus、AtransationStatus、aScalingMatUser参数:
procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
**var** aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
您应该使用var来传递上述过程中的参数,因为没有var
FreeAndNil‘nilles’临时
堆栈变量,它不会
感觉
构造函数调用assign
临时堆栈中的值
变量,并产生内存
泄漏。
为什么错误代码有时工作正常,甚至可能不会导致内存泄漏,这是另一个问题
再编辑一次
正如您已经提到的,Delphi对象是一个引用。因此,您不需要使用var来更改对象。但是您的过程是不同的-它会更改引用本身,而不仅仅是这些引用所指向的数据,因此您应该逐个引用地传递这些引用ArotationStatuser、AtransationStatuser、aScalingMatUser。这就是为什么需要变量。代码
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;
这是一个错误。您应该通过引用传递ArotationStatus、AtransationStatus、aScalingMatUser参数:
procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
**var** aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
您应该使用var来传递上述过程中的参数,因为没有var
FreeAndNil‘nilles’临时
堆栈变量,它不会
感觉
构造函数调用assign
临时堆栈中的值
变量,并产生内存
泄漏。
为什么错误代码有时工作正常,甚至可能不会导致内存泄漏,这是另一个问题
再编辑一次
正如您已经提到的,Delphi对象是一个引用。因此,您不需要使用var来更改对象。但是您的过程是不同的-它会更改引用本身,而不仅仅是这些引用所指向的数据,因此您应该逐个引用地传递这些引用ArotationStatuser、AtransationStatuser、aScalingMatUser。这就是您需要var的原因。@Serg:谢谢您的帮助!那我就不明白为什么当我
打开一个TMolForm?例外情况是随机发生的。此外,由于TMat是类,引用不是已经是传递参数的方式了吗?Delphi类是一个引用,但在上面的例子中需要“引用到引用”procedure@Serg:谢谢你的评论!你能推荐一些我能学到的关于这个术语的材料吗?此外,我仍然不知道为什么它只是随机发生的。尽管如此,我只是添加了var关键字,并将再尝试几分钟,让您知道。@Serg:将var添加到参数列表中可以消除异常!非常感谢你!我不知何故仍然迷茫1为什么在真实代码中异常只是随机产生的2上面的演示代码不产生异常?另外,您是否可以帮助评论在参数中使用var前缀的最佳实践?我的意思是,我似乎记得一个代码示例,其中一个tstringlist被传递以进行修改,但没有var前缀。@Serg:顺便说一下,我找到了这个链接。Nick的答案通过了一个要修改的TStrings实例,但没有var前缀。你能解释一下为什么在这种情况下它是安全的吗?另外,您能否帮助评论一下使用var前缀的最佳做法?@Serg:谢谢您的帮助!那我就不明白为什么我只打开一个TMolForm就可以了?例外情况是随机发生的。此外,由于TMat是类,引用不是已经是传递参数的方式了吗?Delphi类是一个引用,但在上面的例子中需要“引用到引用”procedure@Serg:谢谢你的评论!你能推荐一些我能学到的关于这个术语的材料吗?此外,我仍然不知道为什么它只是随机发生的。尽管如此,我只是添加了var关键字,并将再尝试几分钟,让您知道。@Serg:将var添加到参数列表中可以消除异常!非常感谢你!我不知何故仍然迷茫1为什么在真实代码中异常只是随机产生的2上面的演示代码不产生异常?另外,您是否可以帮助评论在参数中使用var前缀的最佳实践?我的意思是,我似乎记得一个代码示例,其中一个tstringlist被传递以进行修改,但没有var前缀。@Serg:顺便说一下,我找到了这个链接。Nick的答案通过了一个要修改的TStrings实例,但没有var前缀。你能解释一下为什么在这种情况下它是安全的吗?另外,您能否帮助评论一下使用var前缀的最佳实践是什么?如果您担心性能问题,最好使用双精度,而不是extended@David:谢谢你的评论!真的!然后我应该从最佳实践中学习。更重要的是,为了真正的表现,你不希望TVec成为一个班级。最好是一张唱片。您将获得堆栈分配和使用运算符重载的机会。@David Heffernan:非常感谢您的建议!我同意使用记录和堆栈分配可以提高性能。然而,在当前的代码中,TVec有一些程序,如加法/减法/比例/长度等。在Delphi7中,Lazarus试图保持兼容的版本,似乎我无法为记录类型定义函数/过程。操作符重载也是一个非常新的特性。保持代码与Lazarus兼容可以为Mac编译。我使用您的完整用户名,因为StackOverflow说它可以将消息发送给消息接收者。@Xichen好的,我没有意识到您正在尝试跨平台发送消息。你可以考虑使用对象-不知道Lazarus是否支持。这是一个古老的Turbo-Pascal结构。它本质上相当于一个带有方法的记录。对于3向量的堆栈分配,不仅仅是代码运行得更快,而且对于处理此类类型的代码,通常最好使用值类型语义而不是引用语义。那是我个人的看法!如果您担心性能问题,最好使用双精度,而不是extended@David:谢谢你的评论!真的!然后我应该从最佳实践中学习。更重要的是,为了真正的表现,你不希望TVec成为一个班级。最好是一张唱片。您将获得堆栈分配和使用运算符重载的机会。@David Heffernan:非常感谢您的建议!我同意使用记录和堆栈分配可以提高性能。然而,在当前的代码中,TVec有一些程序,如加法/减法/比例/长度等。在Delphi7中,Lazarus试图保持兼容的版本,似乎我无法为记录类型定义函数/过程。操作符重载也是一个非常新的特性。保持代码与Lazarus兼容可以为Mac编译。我使用您的完整用户名,因为StackOverflow说它可以将消息发送给消息接收者。@Xichen好的,我没有意识到您正在尝试跨平台发送消息。你可以考虑使用对象-不知道Lazarus是否支持。这是一个旧的涡轮帕斯卡控制器 uct。它本质上相当于一个带有方法的记录。对于3向量的堆栈分配,不仅仅是代码运行得更快,而且对于处理此类类型的代码,通常最好使用值类型语义而不是引用语义。那是我个人的看法!