delphimemory-leaksoleidispatch

Returning an OleVariant from a function causes an extra reference on the contained IDispatch object that cannot be released


I have a function that returns an OleVariant that contains an IDispatch reference counted object:

class function TGrobber.Make: OleVariant;
begin
   Result := (TGrobber.Create as IDispatch);
   // Reference count of IDispatch inside Result at this point is 1
end;

And the end of the function, the RefCount of the object inside the OleVariant is correctly 1.

But when the OleVariant is returned to the caller, the contained object suddenly has a reference count of 2:

var
    grobber: OleVariant;
begin
    grobber := TGrobber.Make; // function that returns an OleVariant
    // Reference count of IDispatch inside OleVariant is now 2 (wrong)
end;

This is because after Delphi calls the Make function, it calls VarCopy:

Project2.dpr.147: grobber := TGrobber.Make;
004DA79D 8D55E0           lea edx,[ebp-$20]
004DA7A0 A1DC614D00       mov eax,[$004d61dc]
004DA7A5 E8CAC0FFFF       call TGrobber.Make       <== Creates the object (refcount 1)
004DA7AA 8D55E0           lea edx,[ebp-$20]
004DA7AD B800444E00       mov eax,$004e4400
004DA7B2 E8B128F5FF       call @VarCopy            <== Adds an extra reference, loses the old reference

Which means:

How can i ensure that Delphi either:


Solution

  • The compiler does not lose the "old" reference. The result of TGrobber.Make() is first stored in a hidden local variable (located at [ebp-$20]), which is then assigned to your grobber variable (located at $004e4400), hence the copy. So the refcount being 2 is correct. Both variables will be finalized, decrementing the refcount by 2, when they go out of scope when the containing routine exits.

    Your code is effectively doing the equivalent of this:

    var
      hidden: OleVariant;
      grobber: OleVariant;
    begin
      TGrobber.Make(hidden); // function that returns an OleVariant via a hidden var parameter
      grobber := hidden; // VarCopy(grobber, hidden) is called here
      ...
    end; // VarClear(grobber) and VarClear(hidden) are called here...