2

TADOConnection.Execute function returns a _Recordset.

I am currently using this code for simplicity (1):

V := ADOConnection1.Execute(SQL).Fields[0].Value;

I know that the recordset is never empty so no worry about BOF.

Now I can write it like this with a local _Recordset variable (2).

var
  rs: _Recordset; 

  rs := ADOConnection1.Execute(SQL);
  V := rs.Fields[0].Value;

A bit more code.

Now my question is: since the _Recordset is an interface variable returned by Execute function, would it be correctly released if I'm not using a local rs variable (1)? is using my simplified code (1) safe and could there be a reference count issue here?

I would like to get some insights about this issue please.


EDIT: My question is specific to the case:

V := ADOConnection1.Execute(SQL).Fields[0].Value

where I do not have a local variable reference to _Recordset.

zig
  • 4,524
  • 1
  • 24
  • 68
  • Yes, it will be released. It would not be if you would store that reference and keep it stored out of scope of the method in which you executed the query. – TLama Aug 17 '15 at 11:37
  • @TLama, Can you prove it? I am asking because I remember reading about such issues with interfaces created and not assigned to a variable, that had issue with the reference count. old am I just wrong. – zig Aug 17 '15 at 13:54
  • 1
    What @TLama said is correct, but if you have doubts, just set a breakpoint in some line of the code and open the CPU window. Then run the code step-by-step. You will see the precise moments the methods `_AddRef` and `_Release` are called. – AlexSC Aug 17 '15 at 14:49

1 Answers1

4

Try this: Create a procedure that contains the single line

  V := AdoConnection1.Execute(Sql).Fields[0].Value;

, put a breakpoint on it run the app and view the disassembly. You'll see that just before the line

jmp @HandleFinally

there are three calls to

call @IntfClear

That's the compiler releasing the three interfaces it has had to access in order to execute the statement, namely

  • the RecordSet interface returned by AdoConnection1.Execute(),
  • the Fields interface of that RecordSet, and
  • the particular Field interface obtained via Fields[0].

So, it has automatically generated the code necessary to free up these interfaces after executing the source statement.

The following is an imperfect analogy but its disassembly is much easier to follow; it illustrates the code the compiler automatically generates to deal with finalizing interfaces.

Given

type
  IMyInterface = interface
    function GetValue : Integer;
  end;

  TMyClass = class(TInterfacedObject, IMyInterface)
    function GetValue : Integer;
    destructor Destroy; override;
  end;

  TForm1 = class(TForm)
    [...]
    procedure Button1Click(Sender: TObject);
  end;

destructor TMyClass.Destroy;
begin
  inherited;
end;

function TMyClass.GetValue: Integer;
begin
  Result := 1;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I : IMyInterface;
begin
  I := TMyClass.Create;
  Caption := IntToStr(I.GetValue);
end;

the CPU disassembly of Button1Click looks like this

disassembly

and the line arrowed red is where the interface is cleared despite the source code not doing anything explicit to do this. Put a breakpoint on the

inherited

in TMyClass.Destroy and you'll find that also gets called, again despite the source code not explicitly calling it.

Like I said, the above is an imperfect analogy. An interesting thing is that for the horrific (from the pov of the usage of the "with" construct) alternative

procedure TForm1.Button1Click(Sender: TObject);
begin
  with IMyInterface(TMyClass.Create) do
    Caption := IntToStr(GetValue);
end;

which uses no (explicit) local variable, the compiler generates the exact same code as the disassembly illustrated.

Of course, the situation in the q is slightly different because the memory allocated to the recordset object is on the other side of the Ado COM interface, so one has no control over whether that memory is correctly de-allocated beyond the fact that the compiler will generate the code to call _Release on the interface to it.

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • I think you did not understand the question. I have made an edit. – zig Aug 17 '15 at 13:52
  • Actually, with respect I think I did (understand what you were asking), just couldn't think of a way to directly illustrate it. Anyway, see my edit, which adds an alternative which has no explicit local variable. – MartynA Aug 17 '15 at 16:50
  • In reply to another comment (since deleted) about the fact that _Release is called does not guarantee that memory is freed, indeed not, but in the situation under consideration, the memory is allocated by the Ado object implementing the interface, over which Delphi has no control except by following the _AddRef/_Release usage rules. – MartynA Aug 17 '15 at 16:54
  • In regards to memory: https://msdn.microsoft.com/EN-US/library/office/jj249029.aspx states "set the object variable to Nothing (in Visual Basic) after closing the object". Its the "after closing the object" that has me worried. I would explicitly call close and not rely on the _release only – Jasper Schellingerhout Aug 17 '15 at 19:36
  • @Jasper: Why don't you post a q to the effect of "If I instantiate an interface using a Delphi object which implements it, do I need to call Free on the object when I'm done with it?" I'm sure you'll be bombarded by answers saying "Of course not, because the compiler handles that. It's one of the reasons people use interfaces in Delphi, automatic lifetime management." – MartynA Aug 17 '15 at 19:40
  • @JasperSchellingerhout: See e.g. http://stackoverflow.com/questions/5351508/what-are-managed-types-are-they-specific-to-delphi-are-they-specific-to-window – MartynA Aug 17 '15 at 19:48
  • @MartynA I understand about managed types. Your code shows implicit interface references are released. The MS documentation says that to release a recordset the reference must be set to "nothing", that is all good and your sample shows it should behave in line with what is expected. I simply pointed out the MS documentation stating to close before setting to null. – Jasper Schellingerhout Aug 17 '15 at 21:49
  • @JasperSchellingerhout: Sure, but what MS say to do in VB doesn't really have anything to do with what you do - or don't - need to do in Delphi. And this q was about Delphi, not VB. – MartynA Aug 17 '15 at 21:52
  • And, in Delphi, from the D7 era at any rate, a reference is a reference is a reference ... The "implicit" doesn't really have anything to do with how the compiler handles it. – MartynA Aug 17 '15 at 23:13
  • I've added a bit to the end of my answer showing how you can see for yourself the interfaces used in the expression "V := AdoConnection1.Execute(Sql).Fields[0].Value" being cleared by code the compiler automatically generates. – MartynA Aug 18 '15 at 08:35
  • Are you sure that Value is an interface? looks like it's OleVariant. should the compiler use IntfClear on it? – zig Aug 19 '15 at 13:32
  • Oops, you're right about Value, my mistake. But there are still three interfaces involved - see the bullet points in my updated answer. – MartynA Aug 19 '15 at 14:55