Neutral General 15 Posted November 23, 2018 (edited) Hi, I'm trying to write an IDE plugin which extends the popup menu of a project. This is a rough overview of my code: TMyProjectMenuItemNotifier = class(TNotifierObject, IOTANotifier, IOTAProjectMenuItemCreatorNotifier) // ... TMyProjectMenuItem = class(TInterfacedObject, IOTANotifier, IOTALocalMenu, IOTAProjectManagerMenu) // ... { TMyProjectMenuItemNotifier } procedure TMyProjectMenuItemNotifier.Initialize; var manager: IOTAProjectManager; begin if Supports(BorlandIDEServices, IOTAProjectManager, manager) then FProjectNotifierId := manager.AddMenuItemCreatorNotifier(Self); end; procedure TMyProjectMenuItemNotifier.Finalize; var manager: IOTAProjectManager; begin if (FProjectNotifierId > -1) and (Supports(BorlandIDEServices, IOTAProjectManager, manager)) then manager.RemoveMenuItemCreatorNotifier(FProjectNotifierId); end; procedure TMyProjectMenuItemNotifier.AddMenu(const Project: IOTAProject; const IdentList: TStrings; const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean); var item: TMyProjectMenuItem; begin item := TMyProjectMenuItem.Create(Project); item.Caption := 'Test123'; item.Enabled := true; item.Position := 0; ProjectManagerMenuList.Add(item); end; { TMyProjectMenuItem } procedure TMyProjectMenuItem.Execute(const MenuContextList: IInterfaceList); begin ShowMessage('Click!'); end; // Unit which creates and registers all the classes var projectItemNotifier: TMyProjectMenuItemNotifier; procedure Register; begin projectItemNotifier := TMyProjectMenuItemNotifier.Create; projectItemNotifier.Initialize; end; initialization finalization projectItemNotifier.Finalize; In general all this works well so far. There's just one problem: It seems like the TMyProjectMenuItem-Objects are not destroyed on closing the Popup-Menu but on opening the next one. This somehow ends in the following order of events. 1. Build MyPlugin.bpl => Success 2. Install MyPlugin.bpl => Success 3. Right click on my project a) TMyProjectMenuItem is created b) Popup menu shows and my menu item works as expected 4. I close the popup menu 5. I right click on my project (again) a) Prior TMyProjectMenuItem is destroyed b) New TMyProjectMenuItem is created 6. I choose "Uninstall" a) TMyProjectMenuItemNotifier.Finalize is called b) TMyProjectMenuItemNotifier is destroyed 7. I right click on my project (again!) 8. I right click on my project (one last time) => Success. The popup menu with the default items shows The TMyProjectMenuItem from my last click before the uninstall never gets destroyed and it seems like the IDE doesn't know the item is gone either and I get this access violation when the IDE tries to access (and destroy) the last TMyProjectMenuItem. And at this point I'm clueless. I don't know how I'm supposed to fix that. There is no Interface or Method to manually unregister/destroy the object so that the IDE knows it's gone. I'm using Delphi 10.0 Seattle on Windows 10 Edited November 23, 2018 by Neutral General Share this post Link to post
Kryvich 165 Posted November 23, 2018 I have not worked with menu notifiers before, but I think the principle should be the same as with other OTA notifiers. You do not need to store a Notifier object. All you have to store is its index, to remove your notifier when your package is finalized. var MenuItemNotifierInd: Integer; procedure Register; var manager: IOTAProjectManager; projectItemNotifier: IOTAProjectMenuItemCreatorNotifier; begin MenuItemNotifierInd := -1; if not Supports(BorlandIDEServices, IOTAProjectManager, manager) then Exit; projectItemNotifier := TMyProjectMenuItemNotifier.Create; MenuItemNotifierInd := manager.AddMenuItemCreatorNotifier(projectItemNotifier); end; ... finalization if MenuItemNotifierInd >= 0 then //... remove notifier end. For the menu item use an interface instead of an object too: var item: IOTAProjectManagerMenu; begin item := TMyProjectMenuItem.Create(Project); item.Caption := 'Test123'; item.Enabled := true; item.Position := 0; ProjectManagerMenuList.Add(item); Share this post Link to post
Neutral General 15 Posted November 23, 2018 I've tried using an interface for the menu item, but it didn't change anything and every code about this I found also always used object variables so I assumed that this would be okay. Currently my menu notifier also has some other functionality. Implementing the Notifier interface is just one thing it does. But maybe I should use a dedicated notifier object and include my main plugin logic in another class? Share this post Link to post
Kryvich 165 Posted November 23, 2018 (edited) In my IDE plugins I have Data module with main logic and notifiers and other objects, which are created and deleted by the module (DataModuleCreate, DataModuleDestroy). The data module is created and destroyed automatically: initialization DataModule1 := TDataModule1.Create(nil); finalization DataModule1.Free; end. Quote I found also always used object variables so I assumed that this would be okay. When we create an interfaced object and store a reference to it in a variable, its count of references increases. Are you sure that your menu items are really released after use? I followed this instruction when I created my plugins. It recommends to use a Data Module: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Extending_the_IDE_Using_the_Tools_API Edited November 23, 2018 by Kryvich Share this post Link to post
Neutral General 15 Posted November 23, 2018 2 hours ago, Kryvich said: Are you sure that your menu items are really released after use? Yes. I set a breakpoint and before that a ShowMessage in the destructor of the menu items. The old ones get destroyed if a new popup menu is created. I'll take a look at the link, thanks! :) Share this post Link to post