Menu

#261 Reduce menu handle contention

8
pending
1
2025-07-31
2025-04-24
No

Every TFrameWindow, such as TMDIChild, as well as every TView, holds a TMenuDescr, usually assigned at construction. A descriptor inherits from TMenu, and as such, encapsulates a HMENU handle. In some applications, the number of handles held can cause handle exhaustion errors.

See [discussion:13ef3d9b89].

Since menu descriptors are only temporarily needed during menu merging, there is little need to hold these handles permanently in every instance. To lessen the contention, the descriptors can be created on demand, shared, and/or reimplemented to not use a menu handle.

Related

Discussion: TMDIChild::GetMenu returns non-handle!
Discussion: TMDIChild::GetMenu returns non-handle!
Discussion: 13ef3d9b89
Discussion: Running out of (menu) handles
Wiki: OWLNext_Roadmap_and_Prereleases

Discussion

  • Vidar Hasfjord

    Vidar Hasfjord - 2025-04-24

    In [r7731], I have committed an initial implementation of a redesign, providing menu descriptors on demand. Please review and give feedback on the approach.

    The new API removes TFrameWindow::SetMenuDescriptor and makes GetMenuDescriptor virtual with a new signature returning a unique_ptr:

    virtual auto GetMenuDescriptor() const -> std::unique_ptr<TMenuDescriptor>
    {
      const auto m = GetWindowAttr().Menu;
      return m ? std::make_unique<TMenuDescriptor>(m) : nullptr;
    }
    

    In general, to provide a menu descriptor, clients must subclass TFrameWindow and override this function. However, I was able to make a convenient hack in the base implementation: It looks at Attr.Menu, and if set, uses the TResId to load a descriptor from resources. This is particularly useful for TMDIChild and subclasses, since Attr.Menu is unused for children.

    Let's look at how I adapted the Classes example.

    First, to implement GetMenuDescriptor for the main window, I had to create a new subclass TClassesMDIFrame, inheriting from TDecoratedMDIFrame (which was previously used as-is). Conveniently, the implementation encapsulated all menu construction, making AssignMenuBitmaps superfluous:

      class TClassesMDIFrame
        : public TDecoratedMDIFrame
      {
      public:
    
        TClassesMDIFrame(LPCTSTR appName) {...}
    
        auto GetMenuDescriptor() const -> unique_ptr<TMenuDescriptor> override // TFrameWindow
        {
          static const auto id = array {...}; // Menu item bitmap IDs
          auto m = make_unique<TMenuDescriptor>(TResId{IDM_MAINMENU});
          // ...assign menu item bitmaps...
          return m;
        }
      };
    

    Second, I had to adapt the opening of TMDIChild instances for the window examples. The command handlers benefited nicely from the default implementation of GetMenuDescriptor inherited from TFrameWindow, hence requiring just a simple change from SetMenuDescriptor to AssignMenu:

     void TClassesApp::CmTTreeView()
     {
       TMDIChild* child = new TMDIChild(GetMDIClient(), _T("TTreeView"), new TTreeViewWindow(0));
    
    -  child->SetMenuDescriptor(TMenuDescriptor(IDM_TREEVIEWMENU, 0, 0, 0, 1, 0, 0));
    +  child->AssignMenu(IDM_TREEVIEWMENU);
       child->Create();
       child->ShowWindow(SW_SHOW);
     }
    

    However, note that we passed more arguments before, i.e. the detailed popup menu group counts. We cannot specify all this detail when calling AssignMenu, which only sets Attr.Menu (a TResId) for later use. Luckily, it can all be specified in the menu resource instead by carefully inserting five menu item separators demarcating the groups:

    IDM_TREEVIEWMENU MENU
    BEGIN
    
    +    MENUITEM SEPARATOR
    +    MENUITEM SEPARATOR
    +    MENUITEM SEPARATOR
         POPUP "&Item"
         BEGIN
             MENUITEM "&Make selected item bold",    CM_SETSTATE
             MENUITEM "&Check item expansion state", CM_GETSTATE
             MENUITEM "&Sort descending",            CM_SORTDESCENDING
         END
    +    MENUITEM SEPARATOR
    +    MENUITEM SEPARATOR
     END
    

    This makes popup "Item" appear in group 4 as before.

    That's pretty much it!

    Regarding TView related changes, let's look at the adaptations in the RichEdit example:

    void TRichEditorApp::EvNewView(TView& view)
    {
      class TViewHostingMDIChild : public TMDIChild
      {
        TView& View;
    
      public:
    
        TViewHostingMDIChild(TMDIClient& c, TView& v) {...}
    
        auto GetMenuDescriptor() const -> std::unique_ptr<TMenuDescriptor> override
        { return View.GetMenuDescriptor(); }
    
      };
    
      const auto mdiClient = dynamic_cast<TMDIClient*>(GetMainWindow()->GetClientWindow()); CHECK(mdiClient);
      const auto child = new TViewHostingMDIChild{*mdiClient, view};
      child->SetIcon(this, TResId{IDI_DOC});
      child->SetIconSm(this, TResId{IDI_DOC});
      child->Create();
    }
    

    Here, I created a small local class to pass on the menu descriptor from the view to the frame window. Nice and simple, since it works for all the different types of views in the example. Maybe this utility class should be put into the library.

    That said, if you don't need the dynamic delegation to the view, and the view just loads a static menu from resources, there is an even easier way to implement EvNewView: Just pass the TResId of the menu from the view to a regular TMDIChild:

    void TRichEditorApp::EvNewView(TView& view)
    {
      const auto mdiClient = dynamic_cast<TMDIClient*>(GetMainWindow()->GetClientWindow()); CHECK(mdiClient);
      const auto child = new TMDIChild{*mdiClient, nullptr, view.GetWindow()};
      if (const auto m = view.GetMenuDescriptor())
        child->AssignMenu(m->GetId());
      child->SetIcon(this, TResId{IDI_DOC});
      child->SetIconSm(this, TResId{IDI_DOC});
      child->Create();
    }
    

    As TMDIChild inherits the base implementation of GetMenuDescriptor, it will now just create a menu descriptor from the given TResId, whenever needed for main menu merging.

    As for changes to the library, there wasn't much to do. The existing implementation uses the returned pointer just as before, oblivious to the implicit ownership management provided by unique_ptr. A couple of places I had to replace duplicate calls to GetMenuDescriptor by the use of a local variable, but other than that the logic remains the same (except for some minor cleanup and code improvement here and there).

     

    Related

    Commit: [r7731]


    Last edit: Vidar Hasfjord 2025-04-25
  • Vidar Hasfjord

    Vidar Hasfjord - 2025-04-25

    Revision [r7733] removes the vestigial member TFrameWindow::MergeModule. Its getter and setter have been declared deprecated and deleted. Same goes for the GetMenuDescr and SetMenuDescr. Documentation has been added.

     

    Related

    Commit: [r7733]


    Last edit: Vidar Hasfjord 2025-04-25
  • Vidar Hasfjord

    Vidar Hasfjord - 2025-07-31

    Further changes:

    • NEW: TMenuDescriptor is now a value type with move semantics [r8157].
    • NEW: TFrameWindow::GetMenuDescriptor: The default implementation will now return the client window's menu descriptor, if it does not itself have a descriptor assigned and the client window is a TView [r8167].
     

    Related

    Commit: [r8157]
    Commit: [r8167]

  • Vidar Hasfjord

    Vidar Hasfjord - 2025-07-31
    • status: open --> pending
     

Anonymous
Anonymous

Add attachments
Cancel





MongoDB Logo MongoDB