Link to home
Start Free TrialLog in
Avatar of shawn857
shawn857

asked on

Memory leak using records in a TVirtualStringTree

Dear Experts, I'm having a difficult time finding a memory leak caused by the introduction of a TVirtualStringTree into my application. It doesn't help that using a TVirtualStringTree is confusing to me to begin with. I *think* I know what might be causing it, but don't know how/where exactly in my code to free the variables used.  To boil things down to their simplest, I'm reading in some lines of text from a file, then for each line of text, calling a function which adds the whole line to Column 0 of the TVirtualStringTree:

AddWholeRecord(VirtualStringTree1, nil, Inrec);

Open in new window


My function:

function TForm5.AddWholeRecord(aVirtualStringTree: TCustomVirtualStringTree; ANode: PVirtualNode;
 hinrec : string): PVirtualNode;
var
  Data: PTreeData;
  i: integer;
  AVirtualTreeColumn: TVirtualTreeColumn;
begin
  WHILE VIRTUALSTRINGTREE1.HEADER.COLUMNS.COUNT < 1 DO
  BEGIN
    AVirtualTreeColumn:=VirtualStringTree1.Header.Columns.Add;
    AVirtualTreeColumn.Text:='';
    AVirtualTreeColumn.Options:=AVirtualTreeColumn.Options-[coAllowClick];
  END;

  Result := aVirtualStringTree.AddChild(ANode);
  Data := aVirtualStringTree.GetNodeData(Result);
  aVirtualStringTree.ValidateNode(Result, False);
  SetLength(Data^.DataString, 1);
 
  Data^.DataString[0]:=hinrec;

end;  // AddWholeRecord

Open in new window


My GetText event is like so:

procedure TForm5.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: WideString);
Var
  hData: PTreeData;
  hval : integer;
begin
  hData := VirtualStringTree1.GetNodeData(node);
  hval:= High(hData^.DataString);

  if Column <= hval then
     cellText:=hData^.DataString[Column]
   else
     cellText:='';
end;  // VirtualStringTree1GetText

Open in new window


And the declaration of my data structures look like this:

type
  PTreeData = ^TTreeData;
  TTreeData = record
    DataString: array of string;
end;

Open in new window


My thinking is that the creation (and not disposing of) of the local variable "Data: PTreedata;" in function AddWholeRecord is the culprit. I tried adding the line "Dispose(Data)" at the end of that function but it gave a massive runtime error. I think the GetText event is still using the info in variable "Data" well after the AddWholeRecord function completes. At least this is what is seems like as I step through things in debug. To me, I just can't follow when that GetText event gets fired and how many times it runs - it seems to run multiple times for each piece of data and it just doesn't make sense to me. Anyway, would anyone be able to shed some light on where this memory leak might be cropping up and what I'm not disposing of correctly?

Thanks
    Shawn

P.S: I'm using Delphi 7
Avatar of MerijnB
MerijnB
Flag of Netherlands image

I don't see you do a New(), here is how I do it (snippets from your code):

function TForm5.AddWholeRecord(aVirtualStringTree: TCustomVirtualStringTree; ANode: PVirtualNode;
 hinrec : string): PVirtualNode;
var
  Data: PTreeData;
  i: integer;
  AVirtualTreeColumn: TVirtualTreeColumn;
begin
  // snip
  New(Data);
  SetLength(Data^.DataString, 1);
  Data^.DataString[0]:=hinrec;

  Result := aVirtualStringTree.AddChild(ANode, Data);
  aVirtualStringTree.ValidateNode(Result, False);
 

Open in new window


Then make an event handler for OnFreeNode, and do something like:
procedure TForm5.OnFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var Data: PTreeData;
begin
  Data := PTreeData(aVirtualStringTree.GetNodeData(Node)^);
  SetLength(Data^).DataString, 0);
  Dispose(Data);
end;

Open in new window


(caution, not actually tested this, but this is more or less a 1:1 copy of my working code.
Avatar of shawn857
shawn857

ASKER

Thanks Merijn, but that OnFreeNode event met with a catastrophic number of "RunTime Error 216" messages. Several - one after another non-stop. Your code had a couple of compile errors so I had to modify to the best of my knowledge to make it compile. Here are the two routines now (AddWholeRecord and OnFreeNode):

function TForm5.AddWholeRecord(aVirtualStringTree: TCustomVirtualStringTree; ANode: PVirtualNode;
 hinrec : string): PVirtualNode;
var
  Data: PTreeData;
  i: integer;
  AVirtualTreeColumn: TVirtualTreeColumn;
begin
  WHILE VIRTUALSTRINGTREE1.HEADER.COLUMNS.COUNT < 1 DO
  BEGIN
    AVirtualTreeColumn:=VirtualStringTree1.Header.Columns.Add;
    AVirtualTreeColumn.Text:='';
    AVirtualTreeColumn.Options:=AVirtualTreeColumn.Options-[coAllowClick];
  END;

  Result := aVirtualStringTree.AddChild(ANode);

  New(Data);
  Data := aVirtualStringTree.GetNodeData(Result);
  aVirtualStringTree.ValidateNode(Result, False);
  SetLength(Data^.DataString, 1);

  Data^.DataString[0]:=hinrec;
end;  // AddWholeRecord

procedure TForm5.VirtualStringTree1FreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  Data: PTreeData;
  aVirtualStringTree: TCustomVirtualStringTree;
begin
  Data := PTreeData(aVirtualStringTree.GetNodeData(Node)^);
  SetLength(Data^.DataString, 0);
  Dispose(Data);
end;

Open in new window


In the FreeNode event, I added this var declaration:

  aVirtualStringTree: TCustomVirtualStringTree;

Maybe that's not correct, I don't know. Also, this line wouldn't compile:

SetLength(Data^).DataString, 0);

So I modified it to : SetLength(Data^.DataString, 0);

Thanks
   Shawn
Sorry,  misread your code. In OnFreeNode it should be VirtualStringTree1, not aVirtualStringTree. You can remove the variable you added.
Thanks Merijn, but now get the attached error when I close down my app.

After clicking OK on that, appears this:

---------------------------
Application Error
---------------------------
Exception EInvalidPointer in module MyApp.exe at 00002AD8.

Invalid pointer operation.
---------------------------
OK  
---------------------------


Is this line correct:

Data := PTreeData(VirtualStringTree1.GetNodeData(Node)^);

Should that ^ be after (Node) ?

Thanks
    Shawn
FreeNode.JPG
ASKER CERTIFIED SOLUTION
Avatar of MerijnB
MerijnB
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks Merijn, but I'm a little confused still on what I should have in OnFreeNode. I have this currently:

Data := PTreeData(VirtualStringTree1.GetNodeData(Node)^);

Open in new window


... because a couple of posts ago you told me this - "In OnFreeNode it should be VirtualStringTree1, not aVirtualStringTree". But in your latest code, you have:

Data := PTreeData(aVirtualStringTree.GetNodeData(Node)^);

Open in new window


Also, I used your recommended change in GetText and it gives a runtime error right away before it gets to display the records:

---------------------------
MyApp
---------------------------
Access violation at address 00405966 in module 'MyApp.exe'. Read of address 73726942.
---------------------------
OK  
---------------------------


I changed my existing line:

hData := VirtualStringTree1.GetNodeData(node);

Open in new window


...to this that you recommended:

hData := PTreeData(VirtualStringTree1.GetNodeData(node)^);

Open in new window


Putting back in my previous line of code makes the text display fine in the TVirtualStringTree... but the memory leaks that last occurred are back after program closure   :-(

Thanks
   Shawn
Merijn, I think I've got it... found a discrepancy between how I was doing things compared to your full source code. Think I have it straightened out now but still doing some testing. Will let you know soon!

Thanks!
   Shawn
Merijn, it looks like I have this task working now... but I've discovered another memory leak unrelated to the TVirtualStringTree. I'm going to post a new question about that in a few minutes. I'd like to get that new problem solved and make sure everything is working before I wrap up this particular question.

Thanks
   Shawn
Sorry, not a memory leak - but an "Access Violation" when trying to "free" an object....

Shawn
I solved my problem with the "Access Violation" Merijn - used "FreeAndNil" instead of ".Free" and it fixed things... so no need to post another question. And your code suggestions for this question worked perfectly... everything is fine now! Thank you so much!

Cheers
   Shawn
Excellent... thank you!