Search code examples
dynamic-memory-allocationlazarusunicode-stringdelphi

Lazarus/Delphi: UnicodeString in self-allocated record data type causes access violations


I guess, my problem is caused by the concept how UnicodeStrings are implemented, but I cannot solve this problem.

I'm trying to scan a directory tree on disk recursively and build a treeview which should show all files and subfolders. Plus, I want to store additional information for each tree node. The TTreeNode object has only a "Data" property (type Pointer) for this purpose, so I allocate memory manually, store the information and assign the allocated pointer to my data property. Everything seems to work fine unless I include usage of a UnicodeString field within my data record.

So, here is my custom data record definition:

type
  TFileInformation = record
    AbsoluteFileName: UnicodeString;
    FileSize: Int64;
    FileAttributes: LongInt;
    CreationTime, ModificationTime: TDateTime;
  end;

And here is my code for directory recusion:

const NO_ERROR = 0;

procedure ScanDirectory(Folder: UnicodeString; Node: TTreeNode);

var
  Details: Pointer;
  NewNode: TTreeNode;
  SearchAttributes: LongInt;
  SearchMask: UnicodeString;
  SearchRecord: TUnicodeSearchRec;

begin
  if (Folder <> '') and (Folder[Length(Folder)] <> DirectorySeparator) then begin
    Folder += DirectorySeparator;
  end;
  SearchMask := Folder + '*'{$IFDEF WINDOWS} + '.*'{$ENDIF};
  SearchAttributes := faReadOnly or faHidden or faSysFile or faDirectory or faArchive or faSymLink;

  if FindFirst(SearchMask, SearchAttributes, SearchRecord) = NO_ERROR then begin
    repeat
      if ((SearchRecord.Attr and faDirectory) <> faDirectory) or
         ((SearchRecord.Name <> '.') and (SearchRecord.Name <> '..')) then begin
        Details := MemAlloc(SizeOf(TFileInformation));
        //TFileInformation(Details^).AbsoluteFileName := Folder + SearchRecord.Name;
        TFileInformation(Details^).FileAttributes := SearchRecord.Attr;
        TFileInformation(Details^).FileSize := SearchRecord.Size;
        TFileInformation(Details^).CreationTime := SearchRecord.Time;
        //TFileInformation(Details^).ModificationTime := -1;
        if Node = nil then begin
          NewNode := self.trvOrigin.Items.AddNode(nil, nil, ansistring(SearchRecord.Name), Details, naAdd);
        end else begin
          NewNode := self.trvOrigin.Items.AddNode(nil, Node, ansistring(SearchRecord.Name), Details, naAddChild);
        end;

        if (SearchRecord.Attr and (faDirectory or faSymLink)) = faDirectory then begin
          // only recurse folders which are NOT SymLink:
          ScanDirectory(Folder + SearchRecord.Name, NewNode);
        end;
      end;
    until FindNext(SearchRecord) <> NO_ERROR;
  end;
  FindClose(SearchRecord);
end;

When I uncomment the line containing .AbsoluteFileName :=, I get an Access Violation (SIGSEGV-Exception in Unix). I'm currently using Lazarus in objfpc mode on Debian Linux, but I guess it's the same with Delphi on Windows. The Treeview.Data property value is stored in the "Details" variable in my example code, self.trvOrigin is my treeview control.


Solution

  • When you allocate the Details record, the memory is not defined.

    AbsoluteFileName is a managed type and must be properly initialized before use. You need to clear the memory after the allocation:

    FillChar(Details^, SizeOf(TFileInformation), #0);
    

    As an alternative, use New(Details) in combination with Dispose(Details). They will correctly initialize/finalize the record.

    Note: Details must be a typed pointer, PFileInformation.