4

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?

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
  • It would have been really very easy to make an SSCCE. Now I've got to do that. And so has anyone else that wants to run code. In fact, I'm not sure I can even be bothered. Couldn't you do it? Actually, I know the answer without running anything. You got lucky! – David Heffernan Apr 03 '14 at 18:51
  • See, I learn something new and unexpected with each SO question I place. Didn't know about SSCCE. Mine's based on https://github.com/stijnsanders/TMongoWire/blob/master/bsonDoc.pas see it here https://gist.github.com/stijnsanders/9960826 – Stijn Sanders Apr 03 '14 at 19:10
  • I've added an SSCCE to my answer. – David Heffernan Apr 03 '14 at 19:12
  • How do you do it! An already clear and informative answer embellished with explanatory code. +10 for the correct answer (and none for me :-/ SO is cruel but honest) – Stijn Sanders Apr 03 '14 at 19:14
  • Well, I've seen this problem maybe a hundred times. After a while it gets familiar. It's as old as the hills. I've been caught out many times. Thankfully fastmm finds new errors immediately. It just took me a little while to see that's what was happening. I first thought it was a hidden temp, but no, much simpler. – David Heffernan Apr 03 '14 at 19:17
  • I know I go on about this, but making a good SSCCE is invariably the route to solving a problem. Once you get the hang of how to do it you'll find that you won't need to ask very often because you can work it out yourself. The act of simplifying and isolating is enough to take you to the solution. I mean, we all like answering questions here, but I also like trying to help teach others how to solve problems. Lecture over! ;-) – David Heffernan Apr 03 '14 at 19:25
  • It would be interesting to test the problem snippet with FPC instead of Delphi because AFAIR FPC uses different approach to treat 'garbage' interface references. – kludg Apr 04 '14 at 09:26

1 Answers1

6

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
Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @user246408 Yes, removing `const` in the `Save` function would solve the problem. Then a reference would be taken when `Save` begins, and then that reference would be released when `Save` returns. At which point the object would have no references and would be destroyed. – David Heffernan Apr 04 '14 at 13:30
  • 1
    Maybe it is true for the concrete 1st OP snippet, but generally the Delphi compiler only guarantees that garbage interface references are nilled when they go out of scope, and if you produce a SSCCE that solves the problem by removing the `const` specifier I believe I can easily create a counter SSCCE which still has the same OP problem both with and without `const` specifier. – kludg Apr 04 '14 at 13:54
  • @user246408 I don't disagree. I was just answering the question as asked, and considering the two code snippets in front of me. – David Heffernan Apr 04 '14 at 13:57