0

我正在开发一个 MDI 应用程序(Delphi 7),它将以插件的形式加载 .bpl 包作为 MDI 子项。一个插件只能打开一个实例,但显然可以同时打开多个插件。

我有一个类,它是一个通用类,用于“共享”MDI 父级上可用的某些组件。我通过让公共类在构造每个相关组件时存储一个指向每个相关组件的指针来实现这一点。

例如:

...
TCommonClass = class(TObject)
  public
    MainMenu:   ^TMainMenu;
    MyClass:    ^TMyClass;
...
constructor TCommonClass.Create;
var
  CtrlItm: array[0..999] of TComponent;
...
  for i := 0 to (Application.MainForm.ComponentCount - 1) do
  begin
    CtrlItm[i] := Application.MainForm.Components[i];
    if CtrlItm[i].ClassName = ‘TMainMenu’ then MainMenu := @CtrlItm[i];
    if CtrlItm[i].ClassName = ‘TMyClass’  then MyClass  := @CtrlItm[i];
  end;

每当我引用一个对象时,我只需执行以下操作:

  ...
  var
    tmp: String;
  begin
    MainMenu^.items[0].Caption := 'Something'; //just to demonstrate
    MyClass.DoSomething;
  end;

每个插件都有自己的这个公共类的实例,其理念是对其组件之一的任何更新都会真正更新 MDI 父级上的组件。这种方法对我来说一直很有效,直到我编写的最后一个插件(它相当大并且包含许多 TMS 组件)开始给我带来我似乎无法追踪的错误。

我想知道的是,这种方法在内存(指针)使用方面是否合理?通过加载和卸载包,内存映射的变化是否有可能破坏指针?我应该以不同的方式做这件事吗?

4

2 回答 2

2

对于任何涉及在 delphi 中无缘无故使用显式指针语法的问题,适当的反应是从头到脚颤抖,然后等待一分钟,让恶心的感觉过去。

从 TObject 继承的任何对象都已经是按引用的,并且您正在考虑的指针逻辑是 (a) 不必要的第二级指针和 (b) 可能导致错误。

看看这段代码:

var
  a : TMyObject;
  b : TMyObject;
begin
   a := TMyObject.Create;
   a.Name := 'Test';
   b := a;
end;

如果编译该代码,那么在我们分配 b := a 之后 b.Name 的值是多少?这将是“测试”,因为 a 和 b 只是对同一对象的变量引用。因此,对于 TMyClass,您只需将一个值分配给另一个,您将不会复制和创建两个对象,您将拥有两个变量,每个变量都引用相同的对象。

什么是参考?引用是具有更简单和更安全语义的指针。你不能取消引用它(它是自动完成的),你不能忘记这样做(它总是为你完成的)。

简而言之,可以随意将 CLASSES 的引用视为指针。

但是,在 TMainMenu 的情况下,您实际上并不需要共享单个 TMainMenu 实例。事实上,如果你尝试,我想你会发现你有问题,可能是崩溃,或者视觉绘画问题。你打算用“共享”的 TMainMenu 做什么?你没有对你认为你可以用它做什么做任何解释,我怀疑如果你表达得更好一点,你会发现你通过共享 TMainMenu 引用来找错树了。

您会看到,TMainMenu 知道它的父对象,并且您不能将 SAME 对象作为两个不同形式的父对象而不引起问题。当您在 MDI 客户端表单的上下文中使用它时,您应该找到另一个解决方案...您可以使用 Actionmanager 或 TActionList 来实现插件系统,或者只需创建您自己的 IPluginCommand 接口,主表单会枚举该接口并通过抽象你的插件来创建菜单项,因为它们知道它们是显示为菜单项还是其他东西。如果您想要做的只是让您的插件可以看到主菜单,以便您的插件可以在运行时添加更多项目,那么您可以这样做(尽管我认为这很严重并且违反了 OOP 原则),

您可能想要做的是使用 ActionManager 组件或 ActionList 组件,并有两种形式,它们都具有来自共享 ActionList 或 ActionManager 的操作。将 actionlist 或 manager 放到一个名为 SharedActions 的数据模块中,并在两个表单的 uses 子句中添加 SharedActionsDataMod 单元,您可以在运行时看到这些操作,然后可以使用它来制作共享操作的菜单(类似于菜单项存储在菜单之外),随心所欲。

更新由于您询问了菜单但并不真正关心菜单,因此您获得了不适用于您的信息。请不要那样做。如果您只想要一个通用的插件系统,请考虑使用 Interfaces 并制作一个稳定的二进制接口(称为 an ABI),因为这是制作一个真正稳定的插件系统的要求,特别是如果您希望能够从 DLL 动态加载插件或BPL。此外,您必须在链接器设置中启用运行时包 (BPL),以便您可以TForm在不同的二进制模块之间共享对类和其他核心 VCL 类的引用。如果您不使用运行时包,您将静态链接不同的TFormTButton以及其他所有内容,放入您构建的每个 .DLL 和 .EXE 中。

于 2013-03-01T00:25:44.187 回答
2

您不需要当前使用的额外级别的指针间接。你可以瘦下来,例如:

TCommonClass = class(TObject)
public
  MainMenu:   TMainMenu;
  MyClass:    TMyClass;
  ...
end;

constructor TCommonClass.Create;
var
  Ctrl: TComponent;
  ...
begin
  ...
  for i := 0 to (Application.MainForm.ComponentCount - 1) do
  begin
    CtrlItm := Application.MainForm.Components[i];
    if CtrlItm.ClassName = 'TMainMenu' then MainMenu := TMainMenu(CtrlItm);
    else if CtrlItm.ClassName = 'TMyClass' then MyClass := TMyClass(CtrlItm);
    ...
  end;
  ...
end;

.

var
  tmp: String;
begin
  MainMenu.Items[0].Caption := 'Something'; //just to demonstrate
  MyClass.DoSomething;
end;

现在,话虽如此,我建议另一种方法。让 MainForm 本身提供指向每个插件的指针,而不是让插件为它们轮询 MainForm。MainForm 知道所有的指针,所以让它在自己的本地记录中收集指针,并将指向该记录的指针传递给它加载的每个插件。如果任何指针发生变化,所有活动插件将自动拥有最新的指针,而无需为此做任何额外的事情。在每个插件中,只需在访问它之前检查每个指针是否为 nil。例如:

主窗体:

type
  PSharedPointers = ^TSharedPointers;
  TSharedPointers = record
    MainMenu: TMainMenu;
    MyClass:  TMyClass;
    ...
  end;

var
  SharedPointers: TSharedPointers;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  SharedPointers.MainMenu := MainMenu1;
  ...
end;

procedure TMainForm.LoadAPlugin;
type
  InitProc = procedure(Pointers: PSharedPointers);
var
  PluginInst: HInstance;
  Init: InitProc;
begin
  PluginInst := LoadPackage('plugin.bpl');
  @Init := GetProcAddress(PluginInst, 'InitPlugin');
  Init(@SharedPointers);
end;

插入:

type
  PSharedPointers = ^TSharedPointers;
  TSharedPointers = record
    MainMenu: TMainMenu;
    MyClass:  TMyClass;
    ...
  end;

var
  SharedPointers: PSharedPointers = nil;

procedure InitPlugin(Pointers: PSharedPointers);
begin
  SharedPointers := Pointers;
end;

...

var
  tmp: String;
begin
  if SharedPointers.MainMenu <> nil then
    SharedPointers.MainMenu.Items[0].Caption := 'Something'; //just to demonstrate

  if SharedPointers.MyClass <> nil then
    SharedPointers.MyClass.DoSomething;
end;

exports
  InitPlugin;
于 2013-03-01T01:21:39.360 回答