2013-03-01 67 views
0

我正在開發一個MDI應用程序(Delphi 7),它將以插件的形式加載.bpl包作爲MDI子代。只能打開一個插件實例,但顯然可以同時打開多個插件。使用指針共享組件的Delphi 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

你的方法看上去是可行的,我和無記憶的問題,因爲每個指針只是4或8個字節,所以千個指針是隻有4或8K的內存,但另一方面,它看起來像你沒有意識到在軟件包之間共享類和對象的能力。例如,您的主窗體可以提供(或實現)一個接口,您可以從任何包插件直接從該接口窗體請求主菜單或任何其他對象。 – jachguate 2013-03-01 00:34:29

回答

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知道所有的指針,因此它可以在自己的本地記錄中收集指針,並將指向該記錄的指針傳遞給它加載的每個插件。如果任何一個指針改變了,所有活動的插件將自動擁有最新的指針,而不必爲此做任何額外的事情。在每個插件中,只需在訪問它之前檢查每個指針爲零。例如:

的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; 
+0

行動會好得多 – 2013-03-01 23:53:05

+0

@Warren P - 感謝您花時間回覆。 TMainMenu僅僅是我用於這個問題的一個例子。當然,這並不是很好的想法,但你的見解仍然值得讚賞。雖然我確實使用了TMainMenu組件,但是使用MDI應用程序(無疑您知道),MDI子菜單中的菜單會自動與MDI父級的菜單集成 - 所以我在這裏沒有任何問題。 – JorgeJ 2013-03-04 09:19:55

+0

@Remy Lebeau - 感謝您的回覆。我從那以後就想出了問題不在於我使用指針。但是,我已根據您的建議更改了我的代碼,因爲它似乎比我的方法更加結構化和高效。感謝您的幫助。 – JorgeJ 2013-03-04 09:21:29

2

對於任何涉及delphi中顯式指針語法的無端使用問題,適當的迴應是從頭到腳顫抖,然後等待一分鐘,而噁心的感覺經過。

從TObject繼承的任何對象都已經通過引用,並且您正在考慮的指針邏輯是(a)不必要的第二級指針和(b)可能導致錯誤的地方。

看看這段代碼:

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

如果代碼編譯,將b.Name的價值是什麼後,我們分配B:=一個?這將是'測試',因爲a和b只是對同一對象的變量REFERENCES。因此,對於TMyClass,您只需將值分配給另一個值,並且不會複製和創建兩個對象,則會有兩個變量,每個都引用SAME對象。

什麼是參考?引用是一個更簡單,更安全的語義指針。你不能解除引用(它是自動完成的),你不能忘記這麼做(它總是爲你完成)。

總之,隨意將對CLASSES的引用視爲指針。

但是,在TMainMenu的情況下,您並不需要共享一個TMainMenu實例。事實上,如果你嘗試,我認爲你會發現你有問題,可能是崩潰或視覺繪畫問題。你打算如何處理「共享」TMainMenu?你還沒有對你認爲你可以用它做什麼解釋,我懷疑如果你表達得更好一些,你會發現你在分享TMainMenu參考時吠叫了錯誤的樹。

你看,TMainMenu知道它是父對象,並且你不能讓SAME對象父對象成兩種不同的形式而不會造成問題。正如您在MDI客戶端表單的上下文中使用它,您應該找到另一個解決方案...您可以使用Actionmanager或TActionList實現一個插件系統,或者簡單地創建您自己的IPluginCommand接口,主窗體枚舉並創建菜單項,從抽象插件知道它們是否顯示爲菜單項,或者別的東西。如果你所要做的只是讓你的插件可以看到主菜單,那麼你的插件可以在運行時添加更多的項目,你可以這樣做(儘管我認爲這是粗略的,違反了OOP原則),而且你可以通過一個引用到一個TMainMenu插入你的插件,但沒有^指針表示法。

你可能想要做的,而不是什麼是使用ActionManager部件或組件的ActionList,並有兩種形式兩者有來自共享的ActionList或ActionManager行動。將操作列表或管理器放到一個名爲SharedActions的數據模塊中,並將SharedActionsDataMod單元添加到兩個表單的uses子句中,然後您可以在運行時看到這些操作,然後可以使用這些操作來製作共享操作的菜單(就像菜單項存儲在菜單外)儘可能多。

更新由於您詢問了有關菜單,但並未真正關心菜單,您收到的信息並不適用於您。請不要這樣做。如果你只是想一個普通的插件系統,考慮使用界面,使一個穩定的二進制接口(被稱爲ABI),因爲這是做一個真正穩定的插件系統的要求,特別是如果你希望能夠從動態加載的插件一個DLL或BPL。此外,你必須在你的鏈接器設置打開使用運行時包(BPLs),讓您可以共享像TForm等核心VCL類類的引用,不同的二進制模塊之間。如果你不使用運行時軟件包,你將靜態鏈接一個不同的TFormTButton以及其他所有東西,放到你構建的每個.DLL和.EXE文件中。