0

MDI の子としてプラグインの形式で .bpl パッケージをロードする MDI アプリケーション (Delphi 7) を開発しています。開くことができるプラグインのインスタンスは 1 つだけですが、明らかに複数のプラグインを同時に開くことができます。

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;

すべてのプラグインには、この共通クラスの独自のインスタンスがあり、そのコンポーネントの 1 つを更新すると、MDI 親のコンポーネントが実際に更新されるという考えがあります。このアプローチは、私が最後に書いたプラグイン (かなり大きく、多くの TMS コンポーネントを含む) が追跡できないように見えるエラーを出し始めるまで、うまく機能していました。

私が知りたいのは、このアプローチがメモリ (ポインター) の使用に関して健全であるかどうかです。パッケージのロードとアンロードによって、メモリ マッピングの変更によってポインタが破損する可能性はありますか? これを別の方法で行う必要がありますか?

4

2 に答える 2

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 に答える
2

Delphi での明示的なポインタ構文の不当な使用に関する質問に対する適切な応答は、頭から足まで震え、吐き気の感覚がなくなるまで少し待つことです。

TObject から継承するすべてのオブジェクトは既に参照渡しであり、検討しているポインター ロジックは (a) 不要な第 2 レベルのポインターであり、(b) エラーを引き起こす可能性があります。

このコードを見てください:

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

そのコードをコンパイルすると、b := a を代入した後の b.Name の値はどうなるでしょうか? a と b は同じオブジェクトへの単なる変数 REFERENCES であるため、'Test' になります。したがって、TMyClass の場合、一方の値を他方に割り当てるだけで、2 つのオブジェクトをコピーして作成することはなく、それぞれが同じオブジェクトを参照する 2 つの変数を持つことになります。

参照とは何ですか?参照は、より単純で安全なセマンティクスを持つポインターです。逆参照することはできません (自動的に行われます) し、忘れることもできません (常に自動的に行われます)。

つまり、CLASSES への参照をポインタとして自由に扱ってください。

ただし、TMainMenu の場合、単一の TMainMenu インスタンスを共有する必要はありません。実際、試してみると、問題、おそらくクラッシュ、または視覚的な描画の問題があることがわかると思います。「共有」TMainMenu で何をする予定でしたか? あなたはそれを使って何をしようと考えているかについて何の説明もしていません。TMainMenu 参照を共有することで、間違ったツリーを吠えていることがわかると思います。

おわかりのように、TMainMenu はその親オブジェクトを認識しており、同じオブジェクトを 2 つの異なるフォームの親にすることはできず、問題は発生しません。MDI クライアント フォームのコンテキストで使用しているため、別の解決策を見つける必要があります。たとえば、Actionmanager や TActionList を使用するか、メイン フォームが列挙する独自の IPluginCommand インターフェイスを作成するだけで、プラグイン システムを実装できます。また、プラグインがメニュー項目として表示されているか、または他の何かとして表示されているかを把握して、プラグインを抽象化してメニュー項目を作成します。実行時にプラグインがより多くの項目を追加できるように、メイン メニューをプラグインに表示することだけが必要な場合は、それを行うことができます (ただし、それは粗野であり、OOP の原則に違反していると思います)。

代わりに、ActionManager コンポーネントまたは ActionList コンポーネントを使用して、共有 ActionList または ActionManager からのアクションを持つ 2 つのフォームを使用することをお勧めします。アクションリストまたはマネージャーを SharedActions というデータ モジュールに配置し、両方のフォームの uses 句に SharedActionsDataMod ユニットを追加すると、実行時にアクションを確認できます。これを使用して、アクション (メニュー項目のようなもの) を共有するメニューを作成できます。メニューの外に保存されます)好きなだけ。

更新メニューについて尋ねたが、メニューをあまり気にしなかったため、あなたには当てはまらない情報が得られました。お願い、それはやめて。一般的なプラグイン システムだけが必要な場合は、インターフェイスを使用して安定したバイナリ インターフェイス( として知られるABI) を作成することを検討してください。 BPL。また、リンカー設定でランタイム パッケージ (BPL) の使用を有効にする必要があります。これにより、さまざまなバイナリ モジュール間で、およびその他のコア VCL クラスなどのクラスへの参照を共有できますTForm。ランタイム パッケージを使用しない場合は、別のパッケージを静的にリンクすることになりますTFormTButtonおよびその他すべてを、作成した各 .DLL および .EXE に含めます。

于 2013-03-01T00:25:44.187 に答える