我正在开发一个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组件)开始给我错误,我似乎无法追查。
我想知道的是,这种方法在内存(指针)使用方面是否合理?通过加载和卸载包,内存映射的更改是否有可能破坏指针?我应该这样做吗?
答案 0 :(得分:2)
对涉及在delphi中无偿使用显式指针语法的任何问题的适当回应是从头到脚颤抖,然后在恶心的感觉过去等待一分钟。
从TObject继承的任何对象都已经被引用,你正在考虑的指针逻辑是(a)不必要的第二级指针和(b)可能导致错误。
看看这段代码:
var
a : TMyObject;
b : TMyObject;
begin
a := TMyObject.Create;
a.Name := 'Test';
b := a;
end;
如果编译了那段代码,那么在我们分配b:= a之后b.Name的值是多少?它将是'Test',因为a和b只是对同一对象的变量REFERENCES。因此,对于TMyClass,您只需将一个值分配给另一个,并且您不会复制和创建两个对象,您将拥有两个变量,每个变量都引用SAME对象。
什么是参考?引用是具有更简单和更安全的语义的指针。你不能取消引用它(它是自动完成的)你不能忘记这样做(它总是为你完成)。
简而言之,请随意将对CLASSES的引用视为指针。
但是,对于TMainMenu,您实际上不需要共享一个TMainMenu实例。事实上,如果你尝试,我想你会发现你有问题,可能是崩溃或视觉绘画问题。你打算用“共享”TMainMenu做什么?你没有对你认为用它做什么做出任何解释,我怀疑如果你表达得更好,你会发现你在共享TMainMenu参考时咆哮错误的树。
你看,TMainMenu知道它的父对象,你不能让SAME对象成为两种不同的形式,而不会造成问题。当您在MDI客户端表单的上下文中使用它时,您应该找到另一个解决方案......您可以使用Actionmanager或TActionList实现插件系统,或者只需创建自己的IPluginCommand接口,主表单枚举并通过抽象插件来创建菜单项,从而了解它们是否显示为菜单项或其他内容。如果您只想让插件显示主菜单,那么您的插件可以在运行时添加更多项目,您可以这样做(虽然我认为这是严重的并且违反了OOP原则),您可能只是传递参考将TMainMenu插入到您的插件中,但没有^指针表示法。
您可能想要做的是使用ActionManager组件或ActionList组件,并且有两个表单,这两个表单都具有来自共享ActionList或ActionManager的操作。将动作列表或管理器放在名为SharedActions的数据模块上,并在两个窗体的uses子句中添加SharedActionsDataMod单元,您可以在运行时看到这些动作,然后可以用来制作共享动作的菜单(就像菜单项一样)存储在菜单之外)尽可能多。
更新由于您询问了菜单但并不真正关心菜单,因此您获得的信息并不适用于您。请不要这样做。如果你只是想要一个通用的插件系统,那么考虑使用接口并建立一个稳定的二进制接口(称为ABI
)因为这是制作一个非常稳定的插件系统的要求,特别是如果你希望能够从DLL或BPL动态加载插件。此外,您必须开启在链接器设置中使用运行时软件包(BPL),以便您可以在不同的二进制文件之间共享对TForm
等类和其他核心VCL类的引用模块。如果您不使用运行时包,那么您将静态地将不同的TForm
和TButton
以及其他所有内容链接到您构建的每个.DLL和.EXE中。
答案 1 :(得分: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。例如:
的MainForm:
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;