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我不同意。我只是回答了问题,并考虑了我面前的两个代码片段。