Delphi:声明变量避免循环引用

时间:2016-07-04 18:53:39

标签: delphi circular-reference

我有一个Delphi单元需要保留各种形式的应用程序的指针,以便稍后对它们进行操作。

为了执行这些操作,我需要将指针强制转换为表单类型,例如。

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

问题是该单元包含在应用程序的所有其他Delphi单元的使用中。因此,虽然我可以在接口部分声明一个简单的Pointer类型,但我无法声明在其他单元中声明的类型(例如单元TfrmMain的{​​{1}})。

我可以通过在实现部分中使用来解决这个问题,例如:

frmMain.pas

但仍然存在一个问题:我需要将变量特定于我的类实例,用于多线程目的,而不是通用的全局变量。 但是我不能把它放在我的interface type TMyThread = class(TThread) Public ptrFrmMain:Pointer ... implementation uses frmMain var CurrentFrmMain: TfrmMain; 类中,因为TmyThread没有在那里声明,我不能把它放在接口部分的使用中。

解决方案是将TfrmMain作为局部变量放在使用它的所有过程中,然后每次都进行CurrentFrmMain转换,但是您知道更好的解决方案吗?

非常感谢你。

2 个答案:

答案 0 :(得分:6)

我根本不会在线程中放置一个Form指针。我会让线程保持回调函数,甚至是接口:

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;

创建线程时,将Form方法分配给那些回调,或者将Form的接口实现传递给线程:

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

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

无论哪种方式,线程根本不需要了解实际的表单,同时仍然满足特定于表单的功能。

答案 1 :(得分:3)

取决于你的意思"保留各种形式的应用程序的指针,以便稍后对它们进行操作。" - 那是什么样的(或种类)工作?这是关于通用软件设计的问题,关于分解,不仅仅是circular reference或任何其他语言特定的问题。

如果您要做的只是在任何表单上进行相同的工作 - 那么您应该从相同的BASE-FORM-CLASS派生您的表单并保留对该基类的引用,而不是对特定表单类的引用。例如,如果您只需要.Release它们,您可以将它们全部保存为TForm类型引用,它们都是从中派生出来的。这只是提取常见抽象接口的典型案例。

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

您还可以将常用功能提取到中间类中,但是在他的答案中显示的像Remy中的MS COM interface。然而,这与MS COM基于的完全不同的存储器模型(ARC one)接近。虽然我不希望TForm具有自动销毁引用计数,但我也不能完全确定它是否会发生,尤其是在继承和复杂的应用程序中。因此,虽然我喜欢这种方法,但我省略了它,因为有时在实践中它可能会导致对象的意外和过早死亡。如果你能确保不会发生这种情况,尽管它可能是最干净的解决方案。

如果您需要执行不同的操作,那么您实际上不仅可以存储对表单本身的引用,还可以存储对软件片段的操作。然后你的线程声明类将构建一个通用框架来保存表单和过程数据单元。然后你会有额外的单位来实现那些要传递的具体行动。

(线程与动作接口单元)== uses ==> (TMyFormABCD单元的操作)< == uses ==(TMyFormABCD表单声明单元)

作为简化选项,您可以使用与表单本身相同的单位声明这些操作。然后你将所有表单单元依赖于线程单元,但是线程单元(重制为通用和特定表单 - 不可知)将不再依赖于任何表单单元。可能它可能被称为"反转控制"。

参见本系列:http://www.uweraabe.de/Blog/2010/08/16/the-visitor-pattern-part-1/

另外一个设计这个的方案,可以看作是实现这些方法的两个方面 - 将使用Windows消息。 您的"常用界面",您的"操作"将由您将制作的自定义WM_xxx消息(整数consts)表示。然后,您的线程将使用PostMessage API向表单发出这些操作的信号。而那些形式 - 通过实现处理这些消息的方法(或通过非实现=忽略这些消息)将提供这些动作实现。

请参阅:http://www.cryer.co.uk/brian/delphi/howto_send_custom_window_message.htm

PostMessage可以从外部线程使用,但不能(轻松)返回值。 SendMessage只能在主Delphi线程中使用。此外,您必须在发布消息之前检查是否MyTargetForm.HandleAllocated()