Delphi 为什么这个TStreamAdapter没有发布?
比较这两个片段:Delphi 为什么这个TStreamAdapter没有发布?,delphi,com,automatic-ref-counting,Delphi,Com,Automatic Ref Counting,比较这两个片段: (d as IPersistStream).Save( TStreamAdapter.Create( TFileStream.Create('test.bin',fmCreate),soOwned),true); (d as IPersistStream).Load( TStreamAdapter.Create( TFileStream.Create('test.bin',fmOpenRead),soOwned)); 这在第二个文件流上失败。创建,因为
(d as IPersistStream).Save(
TStreamAdapter.Create(
TFileStream.Create('test.bin',fmCreate),soOwned),true);
(d as IPersistStream).Load(
TStreamAdapter.Create(
TFileStream.Create('test.bin',fmOpenRead),soOwned));
这在第二个文件流上失败。创建,因为第一个文件流没有被销毁。这很奇怪,因为参数只有一个引用,我认为在关闭Save
调用时它会被破坏。所以我试了一下:
var
x:IStream;
begin
x:=TStreamAdapter.Create(
TFileStream.Create('test.bin',fmCreate),soOwned);
(d as IPersistStream).Save(x,true);
x:=nil;
x:=TStreamAdapter.Create(
TFileStream.Create('test.bin',fmOpenRead),soOwned);
(d as IPersistStream).Load(x);
x:=nil;
这很好用。(但是在没有
x:=nil;
的情况下再次失败)所以不要担心d
,它是IPersistStream
并且运行正常。为什么需要显式的nil
赋值来强制\u Release
调用?这是Delphi 7的已知问题吗?这是因为链接器/编译器开关吗?以下是IPersistStream.Save的声明:
function Save(const stm: IStream; fClearDirty: BOOL): HResult; stdcall;
关键的一点是流参数作为const
传递。这意味着Save
功能不引用IStream
接口。其引用计数既不递增也不递减。既然两者都没有发生,它就永远不会被摧毁
解决这个问题的方法是确保某些东西包含对接口的引用。这就是您在第二个示例中演示的内容
您需要分配到nil
的原因取决于此代码的执行顺序:
x := TStreamAdapter.Create(
TFileStream.Create('test.bin',fmOpenRead),soOwned
);
其发生顺序如下:
t文件流。创建
TStreamAdapter.Create
x.\u释放
以清除旧引用
参考新的IStream
这显然是错误的顺序。在调用TFileStream.Create
之前,需要清除x
根据前Embarcadero编译工程师Barry Kelly的说法。它从来没有被修复过,而我个人已经放弃了这种情况发生的希望
我的SSCCE在这里演示了该问题:
program SO22846335;
{$APPTYPE CONSOLE}
type
TMyInterfaceObject = class(TObject, IInterface)
FRefCount: Integer;
FName: string;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
constructor Create(const Name: string);
destructor Destroy; override;
end;
constructor TMyInterfaceObject.Create(const Name: string);
begin
inherited Create;
FName := Name;
Writeln(FName + ' created');
end;
destructor TMyInterfaceObject.Destroy;
begin
Writeln(FName + ' destroyed');
inherited;
end;
function TMyInterfaceObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NOINTERFACE;
end;
function TMyInterfaceObject._AddRef: Integer;
begin
Writeln(FName + ' _AddRef');
Result := AtomicIncrement(FRefCount);
end;
function TMyInterfaceObject._Release: Integer;
begin
Writeln(FName + ' _Release');
Result := AtomicDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
procedure Foo(const Intf: IInterface);
begin
Writeln('Foo');
end;
procedure Bar(Intf: IInterface);
begin
Writeln('Bar');
end;
begin
Foo(TMyInterfaceObject.Create('Instance1'));
Bar(TMyInterfaceObject.Create('Instance2'));
Readln;
end.
输出
Instance1 created
Foo
Instance2 created
Instance2 _AddRef
Bar
Instance2 _Release
Instance2 destroyed
实例1已创建
福
实例2已创建
实例2\u AddRef
酒吧
实例2\u释放
实例2已销毁
制作SSCCE真的很容易。现在我必须这么做。其他任何想运行代码的人也是如此。事实上,我甚至不确定自己是否会被打扰。你不能这样做吗?事实上,我什么都不做就知道答案了。你真幸运!看,我从我提出的每个问题中学到了一些新的和意想不到的东西。我不知道SSCE的事。我的是基于看这里我在我的答案中添加了一个SSCCE。你是怎么做到的!一个已经清晰且信息丰富的答案,并配有解释代码+正确答案(我没有:-/这是残酷但诚实的)好吧,这个问题我见过大概一百次了。过了一会儿就熟悉了。它和山一样古老。我被抓了很多次。谢天谢地,fastmm会立即发现新的错误。我花了一点时间才明白这是怎么回事。我最初认为它是一个隐藏的临时变量,但不是,更简单。@user246408是的,在保存函数中删除常量将解决这个问题。然后,当Save
开始时将获取一个引用,然后当Save
返回时将释放该引用。在这一点上,对象将没有引用,并将被销毁。对于具体的第一个OP代码段,可能是这样,但一般来说,Delphi编译器只保证垃圾接口引用在超出范围时为零,如果您通过删除const
说明符来生成一个解决问题的SSCCE,我相信我可以轻松创建一个计数器SSCCE,它在使用和不使用const
说明符时仍然存在相同的OP问题。@user246408我不同意。我只是回答了问题,并考虑了我面前的两个代码片段。