使用指针共享组件的Delphi MDI应用程序
我正在开发一个MDI应用程序(Delphi7),它将以插件的形式加载.bpl包作为MDI子项。一个插件只能打开一个实例,但显然可以同时打开多个插件 我有一个类,它是一个公共类,用于“共享”MDI父级上可用的某些组件。我通过让公共类在构建时存储指向每个相关组件的指针来实现这一点 例如:使用指针共享组件的Delphi MDI应用程序,delphi,pointers,mdi,Delphi,Pointers,Mdi,我正在开发一个MDI应用程序(Delphi7),它将以插件的形式加载.bpl包作为MDI子项。一个插件只能打开一个实例,但显然可以同时打开多个插件 我有一个类,它是一个公共类,用于“共享”MDI父级上可用的某些组件。我通过让公共类在构建时存储指向每个相关组件的指针来实现这一点 例如: ... TCommonClass = class(TObject) public MainMenu: ^TMainMenu; MyClass: ^TMyClass; ... const
...
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组件)开始给我错误,我似乎无法跟踪
我想知道的是,这种方法在内存(指针)使用方面是否合理?通过加载和卸载包,内存映射中的更改是否有可能损坏指针?我应该采取不同的做法吗?对于任何涉及在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,您只需将一个值分配给另一个值,就不会复制和创建两个对象,而是有两个变量,每个变量引用同一个对象
什么是推荐信?引用是一个语义更简单、更安全的指针。您不能取消引用它(它是自动完成的),也不能忘记执行它(它总是为您完成的)
简言之,可以将对类的引用视为指针
但是,在TMainMenu的情况下,实际上不需要共享单个TMainMenu实例。事实上,如果你尝试,我想你会发现你有问题,可能是撞车,或是视觉绘画问题。你打算用“共享”菜单做什么?你还没有解释过你认为你可以用它做什么,我怀疑如果你说得更清楚一点,你会发现你分享一个TMainMenu参考是找错了方向
你看,TMainMenu知道它的父对象,你不能让同一个对象成为两个不同形式的父对象而不引起问题。当您在MDI客户端表单的上下文中使用它时,您应该找到另一个解决方案。。。例如,您可以使用Actionmanager或TActionList实现插件系统,或者只需创建您自己的IPluginCommand界面,主窗体通过抽象您的插件来枚举和创建菜单项,以了解它们是否显示为菜单项或其他内容。如果您只想让插件可以看到主菜单,这样插件就可以在运行时添加更多项,那么您可以这样做(尽管我认为这很糟糕,违反了OOP原则),并且您可以将对TMainMenu的引用传递到插件中,而不必使用^指针符号
您可能想做的是使用ActionManager组件或ActionList组件,并且有两种形式,它们都有来自共享ActionList或ActionManager的操作。将actionlist或manager放在名为SharedActions的数据模块上,并在这两个表单的uses子句中添加SharedActionsDataMod单元,您可以在运行时看到这些操作,然后可以使用这些操作创建共享操作的菜单(类似于存储在菜单外的菜单项)
更新因为您询问了菜单,但并不真正关心菜单,所以您得到了不适用于您的信息。请不要那样做。如果你只想要一个通用的插件系统,考虑使用接口并制作一个稳定的二进制接口(称为ABI
),因为这是制作一个真正稳定的插件系统的要求,特别是如果你希望能够从DLL或BPL动态加载插件。此外,您必须在链接器设置中启用运行时包(BPL)的使用,以便您可以在不同的二进制模块之间共享对类(如TForm
和其他核心VCL类)的引用。如果您不使用运行时包,您将静态地将不同的TForm
和TButton
以及其他所有内容链接到您构建的每个.DLL和.EXE中。对于任何涉及在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,您只需将一个值分配给另一个值,就不会复制和创建两个对象,而是有两个变量,每个变量引用同一个对象
什么是推荐信?引用是一个语义更简单、更安全的指针。您不能取消引用它(它是自动完成的),也不能忘记执行它(它总是为您完成的)
简而言之,请随意处理对CLAS的引用
var
tmp: String;
begin
MainMenu.Items[0].Caption := 'Something'; //just to demonstrate
MyClass.DoSomething;
end;
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;