formsclassdelphiaccess-violation

Why do I get an access violation when I create a form within a class if the class instance is not a variable defined in the calling procedure


I am trying to use a class to display a progress indicator.

If I declare ProgressIndicator as a variable within the calling procedure, everything works fine, and ANewForm displays as I would expect.

However, the following code produces an access violation. Can anyone help me to understand why?

unit Main;

interface

*uses
  Winapi.Windows, Vcl.Forms,
  System.Classes, Vcl.Controls, Vcl.StdCtrls,
  Progress;
type
  TProgressIndicator = class
  private
  public
    ANewForm : TForm;
    constructor Create;
  end;
type
  TfmMain = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    ProgressIndicator : TProgressIndicator;
  end;
var
  fmMain: TfmMain;
implementation
{$R *.dfm}
constructor TProgressIndicator.Create;
  begin
     ANewForm := TForm.Create(Application);
     ANewForm.Show;
  end;
procedure TfmMain.Button1Click(Sender: TObject);
  begin
    ProgressIndicator.Create;
  end;
end.

Solution

  • There's a difference between ProgressIndicator.Create and TProgressIndicator.Create. Usually, you want to use the second form which says, "create a new instance of class TProgressIndicator". The first form says, "take an instance of TProgressIndicator which is stored in the variable ProgressIndicator and call its Create method". The problem is, it doesn't create that instance. In your case, ProgressIndicator is nil, because all class members are initialized to a zero-like value at the construction time, which is not a problem per-se - it still is linked to the class data so it can actually call the Create method. The method tries to create the form, which succeeds, and then tries to store it to the ANewForm field, because the in-memory address of ANewForm is Self + offset; for your code, the offset is probably 0, Self is nil, which gives a final address of (nil + 0) = 0 and the memory location 0 is located in a memory page which is prohibited from all access. That's why you get an Access Violation.

    What you want is:

    procedure TfmMain.Button1Click(Sender: TObject);
      begin
        ProgressIndicator := TProgressIndicator.Create;
      end;
    

    That will first create a new instance and then work with that.