delphidelphi-10.1-berlinjedi

TJclStringList crashes on Free


Create a simple VCL application:

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  TForm1 = class(TForm)
   procedure FormDestroy(Sender: TObject);
   procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  JclStringLists;

var
  MyList1: TJclStringList;
  MyList2: TJclStringList;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  MyList1.Free;
  MyList2.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyList1 := TJclStringList.Create;
  MyList2 := TJclStringList.Create;
  MyList1.LoadFromFile('C:\ONE.txt');
  MyList2.LoadFromFile('C:\TWO.txt');
  Self.Caption := Self.Caption + ' ' + IntToStr(MyList1.Count);
  Self.Caption := Self.Caption + ' ' + IntToStr(MyList2.Count);
end;

end.

It crashes in the TForm1.FormDestroy event-handler when attempting to free the MyList1 object instance. Why?


Solution

  • TJclStringList is a reference counted type (it's declared in JCLStringLists.pas as type TJclStringList = class(TJclInterfacedStringList, IInterface, IJclStringList) and implements both _AddRef and _Release to handle reference counting), so you shouldn't be creating them as objects at all, and you shouldn't manually free them - they will automatically be free'd when the reference to them goes out of scope. (This also means you should not declare them as global variables, because you then don't maintain control over their lifetime.)

    The JclStringLists unit provides several functions that will properly create an instance of the interface for you. You can see them in that unit, just above the implementation keyword:

    function JclStringList: IJclStringList; overload;
    function JclStringListStrings(AStrings: TStrings): IJclStringList; overload;
    function JclStringListStrings(const A: array of string): IJclStringList; overload;
    function JclStringList(const A: array of const): IJclStringList; overload;
    function JclStringList(const AText: string): IJclStringList; overload;
    

    The proper way to use TJclStringList to do what you want is something like this:

    unit Unit1;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, JCLStringLists;
    
    type
      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
      private
        { Private declarations }
        MyList1, MyList2: IJCLStringList;  // Note I and not T in type.
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      MyList1 := JclStringList;
      MyList1.LoadFromFile('C:\Work\Data\FirstName.txt');
      MyList2 := JclStringList
      MyList2.LoadFromFile('C:\Work\Data\LastName.txt');
    
      // Only to demonstrate that both files got loaded by the code above.
      Self.Caption := Format('First: %d Last: %d', [MyList1.Count, MyList2.Count]);
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      // Do NOT free the JclStringLists here - they will automatically be released when
      // the form is destroyed because the reference count will reach zero (as long as
      // you don't have any other references to those variables, which by putting them into
      // the private section is unlikely.
    end;
    
    end.