2
votes

I have a DLL application that is loaded in my main application. The DLL contains a form that is created at runtime. The functionality is: In main application I have a menu which whenever pressed calls a procedure from within the DLL. This procedure dynamically creates the form.

procedure doCreateForm;
var
  myForm: TForm1;
begin
  myForm := TForm1.Create(nil)
  try
    ...
  except
    myForm.Free;
  end;
end;

The closing procedures:

procedure CloseWindow(ASender: TForm1);
begin
  FreeAndNil(ASender);
  Application.ProcessMessages;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  CloseWindow(Self);
end;

The problem (access violation) occurs only on the second attempt to create the form. Not first time, not 3rd, 4th, 5th and so on.

So I click on the menu, the form is created (dynamically) and closed (if condition is not satisfied during form create event). I click again on the menu, and when myForm.Create(nil) is called, AV raises. I click again on the menu, and all is ok. I click again and again and again and all is ok. Only when pressed the 2nd time, AV raises. Is there something wrong with dynamic creation of visual form in DLL?

A more detailed explanation:

The chain is:

  1. I create MyForm (myForm := TForm1.Create(nil))

  2. Prior to showing the form I do some conditioning tests.

  3. If all is ok, myForm.Show - this is working fine and I can also close myForm properly

  4. If something is wrong:

a). I create a message form myMessageForm := TMyMessageForm.Create(nil) that contains a closing timer (the form closes after 10s). This form has action:=caFree in onClose event

b). I call myForm.Close. This form has also action:=caFree in onClose event - this form closes before myMessageForm closes (due to the timer present in myMessageForm)

Both forms are created with nil owner, but they are connected in some way (I don't know why). and the destruction of forms is not performed correctly. The next time myForm.Create(nil) or myMessageForm.Create(nil) is called, access violation occurs. The myMessageForm should be created independently from myForm and it's destruction should not condition myForm destruction in any way.

unit1;

procedure doCreateForm;
var
  myForm: TForm1;
begin
  myForm := TForm1.Create(nil)
  try
    with myForm do
      begin
        if <test condition true> then Show
        else
          begin
            ShowErrMessage('Error', 'Error message text', errType);
            Close;
          end;
      end;      
  except
    myForm.Free;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;



unit2;

procedure ShowErrMessage(title, text: string; err: mtErrType);
var
  myMessageForm: TMyMessageForm;
begin
  myMessageForm := TMyMessageForm.Create(nil)
  try
    with myMessageForm do
      begin
        StepDownCounter := 10;
        CloseTimer.Enable := True;
      end;
  except
    myMessageForm.Free;
  end;
end;

procedure TMyMessageForm.CloseTimerTimer(Sender: TObject);
begin
  StepDownCounter := StepDownCounter - 1;
  if (StepDownCounter < 1) then
    begin
      CloseTimer.Enabled := False;
      LabelStepDownText.Visible := False;
      Close;
    end
  else
    begin
      LabelStepDownText.Caption := 'Window will close in ' + IntToStr(StepDownCounter) + 's';
      LabelStepDownText.Visible := True;
    end;
end;

procedure TMyMessageForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;
1
Use Action := caFree; in your OnClose event to free the form. If you were in other event and would like to free the form it-Self, you'd need to use Release.TLama
You're freeing the form from within one of its own methods. That's exactly the same thing as sawing the limb you're standing on off the tree. As @TLama said, either use Action := caFree in your FormClose event, or use Release.Ken White
Prior to posting the question here I used Action := caFree and also ASender.Release on CloseWindow and nothing changed. Same AV occurs. I cannot stop wondering why the AV raises only on the second creation of the form, not 1st, not 3rd, not 4th, and so on ...Bashk
I've edited the post as David suggested. Not it states the problem more detailed.Bashk
An SSCCE is something that I can paste into an editor, compile and run. It takes quite a bit of effort on your part to make one. If you do make one then you get an answer immediately. Because answering then becomes trivially easy. What's more you get the answer to your problem because we actually know for sure, 100%, what your problem is. Your question has already mutated. So my answer appears out of date. Who knows what your real code looks like? You do. Nobody else does. The power of SSCCE is its ability to communicate what your code really is.David Heffernan

1 Answers

1
votes

You are destroying the object that is executing the current method. All code in that object that is executed after the object is destroyed is invalid.

You should set the close action to caFree, or use Release, as has been explained. These work by posting a message to the queue to allow the object to be destroyed after the current method returns. You are subverting that with the call to ProcessMessages which pumps the queue. Remove the call to ProcessMessages.