问题
I am developing an MDI application (Delphi 7) that will load .bpl packages in the form of plugins as the MDI children. Only one instance of a plugin can be opened, but obviously multiple plugins can be opened simultaneously.
I have a class which is a common class that is used to 'share' certain components available on the MDI parent. I achieve this by having the common class store a pointer to each relevant component when it is constructed.
For example:
...
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;
Whenever I refer to an object I simply do as follows:
...
var
tmp: String;
begin
MainMenu^.items[0].Caption := 'Something'; //just to demonstrate
MyClass.DoSomething;
end;
Every plugin will have its own instance of this common class with the idea that any update to one of its components will truly update the component on the MDI parent. This approach has been working well for me until the last plugin I wrote (which is rather large and contains many TMS Components) has started giving me errors which I cannot seem to trace.
What I would like to know is if this approach is sound in terms of memory (pointers) usage? By loading and unloading packages is there a possibility that changes in the memory mapping is corrupting the pointers? Should I be doing this differently?
回答1:
You don't need the extra level of pointer indirection that you are currently using. You can slim that down, eg:
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;
Now, with that said, I would suggest an alternative approach. Have the MainForm itself provide the pointers to each plugin, rather than having the plugins poll the MainForm for them. The MainForm knows all of the pointers, so have it collect the pointers in its own local record and pass a pointer to that record to every plugin that it loads. If any of the pointers change, all active plugins will automatically have the up-to-date pointer without having to do anything extra for that. Inside each plugin, simply check each pointer for nil before accessing it. For example:
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;
Plugin:
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;
回答2:
The appropriate response to any question involving the gratuitous use of explicit pointer syntax in delphi is to shudder from head to foot, and then wait a minute while the feeling of nausea passes.
Any object inheriting from TObject is already by-reference and the pointer logic you're considering is (a) unnecessary second level of pointers and (b) likely to cause errors.
Look at this code:
var
a : TMyObject;
b : TMyObject;
begin
a := TMyObject.Create;
a.Name := 'Test';
b := a;
end;
If that code compiled, what would the value of b.Name be after we assign b := a? It would be 'Test', because a and b are just variable REFERENCES to the same object. So for TMyClass, you can just assign values of one to the other, and you will not be copying and creating TWO objects, you'll have two variables each of which reference the SAME object.
What is a reference? A reference is a pointer with simpler and safer semantics. You can't dereference it (it's done automatically) and you can't forget to do it (it's always done for you).
In short, feel free to treat references to CLASSES as pointers.
However, in the case of TMainMenu, you don't really need to share a single TMainMenu instance. In fact, if you try, I think you'll find you have problems, perhaps crashes, or visual painting problems. What did you plan to do with the "shared" TMainMenu? You haven't made any explanation of what you thought you might do with it, and I suspect if you articulate that a bit better you'll find that you're barking up the wrong tree with sharing a TMainMenu reference.
You see, TMainMenu knows about it's parent object, and you can't have the SAME object parented into two different forms without causing problems. As you're using it in the context of MDI client forms, you should find another solution... You could implement a plugin system using Actionmanager for example, or TActionList, or simply by creating your own IPluginCommand interface, which the main form enumerates and creates menu items from abstracting your plugins from knowing about whether they're being shown as menu items, or something else. If all you wanted to do was have the main Menu visible to your plugin so your plugin could add more items at runtime, you could do that (although I think that's gross and a violation of OOP principles), and you COULD just pass a reference to a TMainMenu into your plugin however without the ^ pointer notation.
What you might want to do instead is use the ActionManager component or ActionList component, and have two forms both of which have actions that come from a shared ActionList or ActionManager. Put the actionlist or manager onto a data module called SharedActions, and add the SharedActionsDataMod unit in both form's uses clauses, and you can see the actions at runtime, which then can be used to make menus that share the actions (which are like menu items stored outside a menu) as much as you like.
Update Since you asked about menus but didn't really care about menus, you got information that didn't apply to you. Please don't do that. If you just want a general plugin system, consider using Interfaces and making a stable binary interface (known as an ABI
) as that's a requirement to make a really stable plugin system, especially if you wish to be able to load plugins dynamically from a DLL or BPL. Also, you have to TURN ON the use of Runtime Packages (BPLs) in your linker settings, so that you can share references to classes like TForm
and other core VCL classes, between different binary modules. If you don't use runtime packages, you'll be statically linking a different TForm
and TButton
and everything else, into each .DLL and .EXE you build.
来源:https://stackoverflow.com/questions/15148136/delphi-mdi-application-with-components-shared-using-pointers