1
votes

I've got a Delphi unit which needs to keep the pointer of various forms of the application, to do operations on them later.

In order to do those operations, I need to cast the pointer to a form type, ex.

var    
  ptrFrmMain: Pointer;
  CurrentFrmMain: TfrmMain;
begin
    CurrentFrmMain := ptrFrmMain;
    CurrentFrmMain.Close();
end;

The problem is that this unit is contained in the uses of all the other Delphi units of the application. So while I can declare a simple Pointer type in the interface section, I cannot declare a type declared in the other units (such as TfrmMain of the unit frmMain.pas).

I could solve this by placing a use in the implementation section, such as:

interface
type TMyThread = class(TThread)
  Public
    ptrFrmMain:Pointer
...

implementation
    uses frmMain

    var
      CurrentFrmMain: TfrmMain;

but there is still a problem: I need the variable to be specific to my class instance, for multithread purposes, and not a generic global variable. But I cannot place it inside my TmyThread class, since TfrmMain is not declared there and I cannot place it in the uses of the interface section.

A solution would be to place CurrentFrmMain as a local variable in all the procedures which use it and then do the CurrentFrmMain := ptrFrmMain conversion each time, but do you know a better solution?

Thank you very much in advance.

2
You can typecast your pointer in-place or you can declare as generic base TForm...Free Consulting
@FreeConsulting I cannot declare as generic base Tform because I need to call functions which are specific of my form, ex. 'CurrentFrmMain.MyFunction()'Flavio
Declare the variable as a TForm and then cast it to the specific form type using as in the implementation section. This does not "reinitialize" anything, it simply refers to the existing form(s) using a reference of the correct type.Rudy Velthuis
No, it would not. As checks if the form is really the type of form you specify and throws and exception if not. It is a type safe cast. You can check the type before with is. Also note that as and is only work on objects, not on untyped pointers.Rudy Velthuis
The general pattern is: if MyForm is TFormABCD then MyFormABCD := MyForm as TFormABCD; // now you can use MyFormABCD.Rudy Velthuis

2 Answers

6
votes

I wouldn't put a Form pointer in the thread at all. I would have the thread hold callback functions instead, or even an interface:

type
  TCloseProc: procedure of object;

  TMyThread = class(TThread)
  public
    CloseProc: TCloseProc;
    ...
  end;

...

begin
  if Assigned(CloseProc) then CloseProc();
end;

type
  IMyIntf = interface(IInterface)
  ['{9CC7DB9E-D47F-4B7D-BBF9-6E9B80823086}']
    procedure DoClose;
  end;

  TMyThread = class(TThread)
  public
    Intf: IMyIntf;
    ...
  end;

...

begin
  if Assigned(Intf) then Intf.DoClose();
end;

...

type
  TfrmMain = class(TForm, IMyIntf)
  public
    procedure doClose;
  end;

procedure TfrmMain.doClose;
begin
  Close;
end;

When the thread is created, assign the Form methods to those callbacks, or pass the Form's interface implementation to the thread:

Thread := TMyThread.Create(True);
Thread.CloseProc := frmMain.Close;
Thread.Resume;

Thread := TMyThread.Create(True);
Thread.Intf := frmMain as IMyIntf;
Thread.Resume;

Either way, the thread doesn't need to know about the actual Forms at all while still catering to Form-specific functionality.

3
votes

Depends upon what do you mean by "keep the pointer of various forms of the application, to do operations on them later." - what kind (or kinds) of work that is? This is a question about generic software design, about decomposition, not just circular reference or any other language-specific issue.

If all you want to do is making same work over any form - then you should derive your forms from the same BASE-FORM-CLASS and keep references to that base class, not to the specific form classes. For example if you just need to .Release them you can just keep them all as TForm type reference which they all are derived from. This is just a typical case of extracting common abstract interface.

TMyFormWithActions = class ( TForm ) .... end;    
TMyForm1234 = class ( TMyFormWithActions ) .... end;
TMyFormABCD = class ( TMyFormWithActions ) .... end;

You can also extract the common functionality not into intermediate class, but into the MS COM interface like Remy shown in his answer. This however is bordering with quite different memory model (ARC one) MS COM was based upon. While I do not expect TForm have auto-destroy reference counting, I also am not totally sure it can't happen, especially in inherited and complex application. So while I do like that approach, I omitted it because sometimes in practice it might cause unexpected and premature death of objects. If you can ensure that would not happen though it might be the most clean solution.


And if you need to do DIFFERENT actions, then you can indeed not merely store references to forms themselves, but also to actions, to software snippets. Then your thread-declaring class would build a general framework to keep forms-and-procedures data cells. And then you would have extra units implementing those specific actions to be passed.

( thread-and-action interface unit ) == uses ==> ( actions for TMyFormABCD unit ) <== uses == ( TMyFormABCD form declaration unit )

As a simplified option, you can declare those actions in the same units as forms themselves. Then you would have all form-units depend upon thread-unit, but thread-unit (remade to be generic and specific forms-agnostic) would no more depend upon any of forms-unit. Probably it might be called "Inversion of control".

See this series: http://www.uweraabe.de/Blog/2010/08/16/the-visitor-pattern-part-1/


And one more scheme to design this, which can be seen as implementing BOTH of those approaches - would be using Windows Messages. Your "common interface", your "actions" would be represented by custom WM_xxx messages (integer consts) you would make. Then your thread would use PostMessage API to signal those actions to the forms. And those forms - by implementing methods to deal with those messages ( or by non-implementing = ignoring those messages ) would provide those action-implementations.

See: http://www.cryer.co.uk/brian/delphi/howto_send_custom_window_message.htm

PostMessage can be used from external thread but can not (easily) return values. SendMessage can only be used from the main Delphi thread. Also you have to check if MyTargetForm.HandleAllocated() before posting messages.