Delphi 中接口的内存管理
Memory management of interfaces in Delphi
我正在努力学习 delphi 和内存管理,来自 C#。
这种斗争的当前体现是,当我处理完这些对象时,我不知道正确的处理方法。从阅读和我的实验看来,如果我有一个被转换为接口的对象,那么我唯一的选择是将引用设置为 nil。
如果我去打电话
免费和零()
我最终遇到访问冲突,例如:
var
foo: IFoo;
begin
foo := TFoo.Create();
FreeandNil(foo);
end;
当然,我需要做的就是改变 foo:IFoo;至 foo:TFoo;它很快乐。或者简单地将指针设置为 nil,而不是调用 freeandNil。
foo := nil;
所以,在一个层面上,我一点也不明白 AV 在哪里。
在不同的层面上,我希望编写的代码不需要知道它是接口还是对象。我希望能够以完全相同的方式编写我所有的内存管理,但我似乎无法编写可以处理 Class 或接口的方法。好吧,那不是真的,我确实有一些东西,但是它太丑了我犹豫了 post 它。
但我想我也应该问,其他人都在做什么?脑子里记着什么是接口,只是 nil 那些指针?否则调用 FreeAndNil?
我打算第一次将事情实现为一个具体的 class,但当我发现代码可以通过 2 种不同的方式执行某些操作时,稍后回来将其更改为接口.而且我不想检查代码并更改它处理该引用的方式,这是我当时最不想考虑的事情。
但为了讨论,我最好的(几乎唯一的)想法是 class:
interface
type
TMemory = class(TObject)
class procedure Free(item: TObject); overload; static;
class procedure Free<T: IInterface>(item: T); overload; static;
end;
implementation
uses
System.SysUtils;
{ TMemory }
class procedure TMemory.Free(item: TObject);
begin
FreeandNil(item);
end;
class procedure TMemory.Free<T>(item: T);
begin
//don't do anything, it is up the caller to always nil after calling.
end;
那么我就可以一直调用:
TMemory.Free(Thing);
Thing := nil;
测试代码:
procedure TDoSomething.MyWorker;
var
foo: IFoo;
fooAsClass: TFoo;
JustAnObject: TObject;
begin
foo := TFoo.Create();
fooAsClass := TFoo.Create();
JustAnObject := TObject.Create();
TMemory.Free(foo);
foo := nil;
TMemory.Free(fooAsClass);
fooAsClass := nil;
TMemory.Free(JustAnObject);
JustAnObject := nil;
end;
运行时没有泄漏或访问冲突。 (使用 MadExcept)
但是非常感谢 Delphi SO 社区。你们是最棒的学习对象!
如果我们通过接口变量访问某个对象,并不总是意味着对象在引用计数器降为零时就被销毁了。例如,TComponent
方法 _AddRef 和 _Release 实现是 'dummy':没有实现引用计数并且 TComponent 永远不会被销毁,因为接口变量超出范围。
为了实现我们对 'real' 接口的预期行为,您的所有对象都应该是 TInterfacedObject
的后代,或者您需要自己实现 _AddRef
/ _Release
。
是的,有 2 种不同的内存管理方法,它们通常共存于一个程序中,但只有在以两种方式处理同一对象时才会出现混淆(和 AV)。如果我们销毁了对象,然后接口变量就超出了范围,它们会调用销毁对象的 _Release
方法,这会导致访问冲突。这是一项有风险的业务,但只要稍加注意,它是可行的。
经典 Delphi 组件不进行引用计数,而是使用所有权的概念。每个组件都有一个所有者,其职责是在它自身被销毁时释放所有内存。所以每个组件都有一个所有者,但它也可能有很多指向另一个组件的指针,比如 Toolbar 有 ImageList
变量。如果这些组件被重新计数,它们将永远不会因为循环引用而被销毁,所以为了打破这个循环,你还需要 'weak' 引用而不是 'count'。他们也在这里,但那是 Delphi 的最新功能。
如果你的对象中有一些层次结构,所以你知道 'bigger' 对象需要所有 'smaller' 对象才能起作用,那么使用这个很好的旧方法,它非常简单并且非常好Delphi 中的实现,即:无论在何处出现异常,您都可以编写无泄漏的代码。有所有这些小事情,比如使用 .Free
而不是 .Destroy
,因为如果在构造函数中发生异常,析构函数会自动调用,等等。事实上非常聪明的解决方案。
只有当您不知道某个对象需要多长时间并且没有合适的 'owner' 时,我才会使用 refcounted 接口。我用一个线程中保存到文件的扫描图像来完成它,同时转换为较小的图像以在另一个线程的屏幕上显示。当一切都完成后,RAM 中不再需要图像并且可以被销毁,但我不知道哪个先发生。在这种情况下,最好使用引用计数。
访问冲突的原因是 FreeAndNil
采用 未类型化 参数,但 需要 它是一个对象。所以方法是对对象进行操作的。
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj); //Obj must be a TObject otherwise all bets are off
Pointer(Obj) := nil; //Will throw an AV if memory violation is detected
Temp.Free; //Will throw an AV if memory violation is detected
end;
如果您销毁一个先前已销毁或从未创建的对象,则上述 中的内存违规可能 (不保证注意)会被检测到。如果 Obj
根本不引用对象而是引用其他对象(例如接口、记录、整数,因为它们没有实现 Free
并且如果它们实现了,它也可能会被检测到与 TObject.Free
).
的定位方式不同
On a differently level, I want to write the code such that it does not need to know if it is an interface or an object. I want to be able to write all of my memory management the same exact way.
这就像说您想以与淋浴完全相同的方式使用汽车。
好吧,也许区别不是那么极端。但重点是接口和对象(以及与此相关的记录)使用不同 内存管理范例。你不能以同样的方式管理他们的内存。
- 需要明确销毁对象。您可以使用所有权模型,但销毁仍然是一个明确的外部操作。
- 接口是引用计数的。编译器注入代码来跟踪引用(查看)底层实例的字段和变量的数量。通常,当最后一个引用被释放时,对象会自行销毁。 (有一些方法 超出了这个答案的范围 来改变它。)
我正在努力学习 delphi 和内存管理,来自 C#。
这种斗争的当前体现是,当我处理完这些对象时,我不知道正确的处理方法。从阅读和我的实验看来,如果我有一个被转换为接口的对象,那么我唯一的选择是将引用设置为 nil。 如果我去打电话 免费和零() 我最终遇到访问冲突,例如:
var
foo: IFoo;
begin
foo := TFoo.Create();
FreeandNil(foo);
end;
当然,我需要做的就是改变 foo:IFoo;至 foo:TFoo;它很快乐。或者简单地将指针设置为 nil,而不是调用 freeandNil。
foo := nil;
所以,在一个层面上,我一点也不明白 AV 在哪里。
在不同的层面上,我希望编写的代码不需要知道它是接口还是对象。我希望能够以完全相同的方式编写我所有的内存管理,但我似乎无法编写可以处理 Class 或接口的方法。好吧,那不是真的,我确实有一些东西,但是它太丑了我犹豫了 post 它。
但我想我也应该问,其他人都在做什么?脑子里记着什么是接口,只是 nil 那些指针?否则调用 FreeAndNil?
我打算第一次将事情实现为一个具体的 class,但当我发现代码可以通过 2 种不同的方式执行某些操作时,稍后回来将其更改为接口.而且我不想检查代码并更改它处理该引用的方式,这是我当时最不想考虑的事情。
但为了讨论,我最好的(几乎唯一的)想法是 class:
interface
type
TMemory = class(TObject)
class procedure Free(item: TObject); overload; static;
class procedure Free<T: IInterface>(item: T); overload; static;
end;
implementation
uses
System.SysUtils;
{ TMemory }
class procedure TMemory.Free(item: TObject);
begin
FreeandNil(item);
end;
class procedure TMemory.Free<T>(item: T);
begin
//don't do anything, it is up the caller to always nil after calling.
end;
那么我就可以一直调用:
TMemory.Free(Thing);
Thing := nil;
测试代码:
procedure TDoSomething.MyWorker;
var
foo: IFoo;
fooAsClass: TFoo;
JustAnObject: TObject;
begin
foo := TFoo.Create();
fooAsClass := TFoo.Create();
JustAnObject := TObject.Create();
TMemory.Free(foo);
foo := nil;
TMemory.Free(fooAsClass);
fooAsClass := nil;
TMemory.Free(JustAnObject);
JustAnObject := nil;
end;
运行时没有泄漏或访问冲突。 (使用 MadExcept)
但是非常感谢 Delphi SO 社区。你们是最棒的学习对象!
如果我们通过接口变量访问某个对象,并不总是意味着对象在引用计数器降为零时就被销毁了。例如,TComponent
方法 _AddRef 和 _Release 实现是 'dummy':没有实现引用计数并且 TComponent 永远不会被销毁,因为接口变量超出范围。
为了实现我们对 'real' 接口的预期行为,您的所有对象都应该是 TInterfacedObject
的后代,或者您需要自己实现 _AddRef
/ _Release
。
是的,有 2 种不同的内存管理方法,它们通常共存于一个程序中,但只有在以两种方式处理同一对象时才会出现混淆(和 AV)。如果我们销毁了对象,然后接口变量就超出了范围,它们会调用销毁对象的 _Release
方法,这会导致访问冲突。这是一项有风险的业务,但只要稍加注意,它是可行的。
经典 Delphi 组件不进行引用计数,而是使用所有权的概念。每个组件都有一个所有者,其职责是在它自身被销毁时释放所有内存。所以每个组件都有一个所有者,但它也可能有很多指向另一个组件的指针,比如 Toolbar 有 ImageList
变量。如果这些组件被重新计数,它们将永远不会因为循环引用而被销毁,所以为了打破这个循环,你还需要 'weak' 引用而不是 'count'。他们也在这里,但那是 Delphi 的最新功能。
如果你的对象中有一些层次结构,所以你知道 'bigger' 对象需要所有 'smaller' 对象才能起作用,那么使用这个很好的旧方法,它非常简单并且非常好Delphi 中的实现,即:无论在何处出现异常,您都可以编写无泄漏的代码。有所有这些小事情,比如使用 .Free
而不是 .Destroy
,因为如果在构造函数中发生异常,析构函数会自动调用,等等。事实上非常聪明的解决方案。
只有当您不知道某个对象需要多长时间并且没有合适的 'owner' 时,我才会使用 refcounted 接口。我用一个线程中保存到文件的扫描图像来完成它,同时转换为较小的图像以在另一个线程的屏幕上显示。当一切都完成后,RAM 中不再需要图像并且可以被销毁,但我不知道哪个先发生。在这种情况下,最好使用引用计数。
访问冲突的原因是 FreeAndNil
采用 未类型化 参数,但 需要 它是一个对象。所以方法是对对象进行操作的。
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj); //Obj must be a TObject otherwise all bets are off
Pointer(Obj) := nil; //Will throw an AV if memory violation is detected
Temp.Free; //Will throw an AV if memory violation is detected
end;
如果您销毁一个先前已销毁或从未创建的对象,则上述 中的内存违规可能 (不保证注意)会被检测到。如果 Obj
根本不引用对象而是引用其他对象(例如接口、记录、整数,因为它们没有实现 Free
并且如果它们实现了,它也可能会被检测到与 TObject.Free
).
On a differently level, I want to write the code such that it does not need to know if it is an interface or an object. I want to be able to write all of my memory management the same exact way.
这就像说您想以与淋浴完全相同的方式使用汽车。
好吧,也许区别不是那么极端。但重点是接口和对象(以及与此相关的记录)使用不同 内存管理范例。你不能以同样的方式管理他们的内存。
- 需要明确销毁对象。您可以使用所有权模型,但销毁仍然是一个明确的外部操作。
- 接口是引用计数的。编译器注入代码来跟踪引用(查看)底层实例的字段和变量的数量。通常,当最后一个引用被释放时,对象会自行销毁。 (有一些方法 超出了这个答案的范围 来改变它。)