Why is this TStreamAdapter not released?

感情迁移 提交于 2020-01-14 13:01:50

问题


Compare these two snippets:

(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));

This fails on the second TFileStream.Create because the first isn't destroyed. This is odd since the parameter has the only reference, I thought it would get destroyed in closing up the Save call. So I tried this:

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;

Which works fine. (But fails again without x:=nil;) So don't worry about d, it is a IPersistStream and is behaving correctly. Why does it take an explicit nil assignment to force the _Release call? Is this a know issue with Delphi 7? Is it because of a linker/compiler switch?


回答1:


Here is the declaration of IPersistStream.Save:

function Save(const stm: IStream; fClearDirty: BOOL): HResult; stdcall;

The key point is that the stream parameter is passed as const. That means that the Save function does not take a reference to the IStream interface. Its reference count is neither incremented or decremented. And since neither happens, it is never destroyed.

The way to work around it is to make sure that something holds a reference to the interface. Which is what you demonstrate in the second example.

The reason that you need the assignment to nil is down to the order in which this code is executed:

x := TStreamAdapter.Create(
  TFileStream.Create('test.bin',fmOpenRead),soOwned
);

It happens in this order:

  1. TFileStream.Create.
  2. TStreamAdapter.Create.
  3. x._Release to clear the old reference.
  4. Take a reference to the new IStream.

And that is clearly in the wrong order. You need to clear x before calling TFileStream.Create.


According to former Embarcadero compiler engineer, Barry Kelly, the issue regarding the interface passed to a const parameter is a bug. It has never been fixed and I for one have given up hope of that ever happening.

My SSCCE to demonstrate the issue is here:

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.

Output

Instance1 created
Foo
Instance2 created
Instance2 _AddRef
Bar
Instance2 _Release
Instance2 destroyed


来源:https://stackoverflow.com/questions/22846335/why-is-this-tstreamadapter-not-released

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!