Search code examples
delphirecordtlistview

Accessing TListItem.Data leads to an error when using records


I have a program which uses TListView to visualize and store some data. TListitem's data property is filled with a pointer to a record like so:

type
  TWatch = record
    name : string;
    path : string;
    //...
end;

procedure TfrmProcessWatcherMain.AddWatchToListView(AWatch: TWatch);
var
  ANewWatch : TListItem;
begin
  ANewWatch := lvWatches.Items.Add; //lvWatches is TListview
  //...
  ANewWatch.Data:= @AWatch;
end;

When I'm trying to retrieve this data somehow I'm getting access violation error, which is a total surprise for me because all seems legit, here is code of retrieval:

if(lvWatches.Selected <> nil) then begin
  tempWatch := TWatch(lvWatches.Selected.Data^); // AV here
  ShowMessage(tempWatch.name);

Also AWatch which is passed to a first function is stored in a

WatchList : TList<TWatch>;

so it is accessible using other methods


Solution

  • The problem is that @AWatch is the address of a local variable. As soon as AddWatchToListView returns AWatch goes out of scope and that address is no longer valid.

    Instead of taking the address of a local variable you need to allocate a record on the heap using New.

    procedure TfrmProcessWatcherMain.AddWatchToListView(AWatch: TWatch);
    var
      ANewWatch : TListItem;
      P : ^TWatch;
    begin
      ANewWatch := lvWatches.Items.Add;
      New(P);
      P^ := AWatch;
      ANewWatch.Data:= P;
    end;
    

    You will need to deallocate the memory with Dispose whenever a list item is destroyed. Do that using the list view's OnDeletion event.

    Alternatively, you could store the index of the item in WatchList. Or the address of the record in WatchList, which you get like this: @WatchList.List[Index]. Both of these options rely on WatchList not being modified after references to items are taken, which may be too constraining for you.