Re: Why AppendMenu fails?

From: Doug Harrison [MVP] (dsh_at_mvps.org)
Date: 05/09/04


Date: Sun, 09 May 2004 14:34:32 -0500

markus wrote:

>I still think it is a size issue in CMenu.
>
>I currently have this loop
>
>for(int i = 0; i < n; i++)
>
> strElement = ...;
> nID = ...;
>
> ASSERT( (*iterMenus)->AppendMenu(MF_STRING ,nID,strElement) );
>
>}
>
>If I make it half the size so that
>
>for(int i = 0; i < n/2; i++)
>{
>...
>}
>
>the assertion does not fail. In first case it fails when
>nID (of type UINT now) is around 23000.
>
>All CMenu objects are local to a button handler in
>a díalog class.

The conventional wisdom is that NT-based Windows has no resource limits such
as the "system resources" that were such a big deal in Win9X. It sure sounds
like you're running out of something, and I don't know if the WinXP limits
are published anywhere, so I wrote a program to see if I could determine
this empirically. This program creates up to MaxMenus popup menus, each
containing up to MaxSize menu items. It deliberately leaks the menus to see
if there is some cumulative limit on menu handles or menu items. My WinXP
results seem to indicate there is a system-wide limit on the number of menu
items, which I would guess is around 32 K total items. I was able to create
menus containing 5,000 items (but not much more than that), all with ID
23000, so that's not it, but if you're leaking menus, or unintentionally
creating huge menus, either could explain your AppendMenu failure. My
program, results, and analysis follow.

*****

#include <afx.h>
#include <afxwin.h>

#include <stdio.h>

const int MaxMenus = 100;
const int MaxSize = 5000;

void test_menu(int& numPopups, int& numItems)
{
   CMenu* m = new CMenu;

   if (!m->CreatePopupMenu())
   {
      puts("Failure: CreatePopupMenu failed.");
      return;
   }

   ++numPopups;

   for (int i = 0; i < MaxSize; ++i)
   {
      if (!m->AppendMenu(MF_STRING, 23000, "x"))
         break;
   }

   int n = m->GetMenuItemCount();
   numItems += n;
   printf("%s: The menu contains %d items.\n",
         (n == MaxSize) ? "Success" : "Failure",
         n);
}

int main()
{
   int numPopups = 0;
   int numItems = 0;

   for (int i = 0; i < MaxMenus; ++i)
   {
      printf("Menu #%d: ", i+1);
      test_menu(numPopups, numItems);
   }

   printf("\nCreated %d menus containing a total of %d items.\n",
         numPopups,
         numItems);

   return 0;
}

*****

E>cl -MD -D_AFXDLL -W3 a.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

a.cpp
 WINVER not defined. Defaulting to 0x0501 (Windows XP and Windows .NET
Server)
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.

/out:a.exe
a.obj
LINK : warning LNK4089: all references to 'OLEAUT32.dll' discarded by
/OPT:REF

*****

E>a
Menu #1: Success: The menu contains 5000 items.
Menu #2: Success: The menu contains 5000 items.
Menu #3: Success: The menu contains 5000 items.
Menu #4: Failure: The menu contains 4584 items.
Menu #5: Failure: The menu contains 1680 items.
Menu #6: Failure: The menu contains 1560 items.
Menu #7: Failure: The menu contains 1552 items.
Menu #8: Failure: The menu contains 1176 items.
Menu #9: Failure: The menu contains 832 items.
Menu #10: Failure: The menu contains 344 items.
Menu #11: Failure: The menu contains 328 items.
Menu #12: Failure: The menu contains 128 items.
Menu #13: Failure: The menu contains 112 items.
Menu #14: Failure: The menu contains 104 items.
Menu #15: Failure: The menu contains 96 items.
Menu #16: Failure: The menu contains 48 items.
Menu #17: Failure: The menu contains 40 items.
Menu #18: Failure: The menu contains 16 items.
Menu #19: Failure: The menu contains 8 items.
Menu #20: Failure: The menu contains 8 items.
Menu #21: Failure: The menu contains 0 items.
Menu #22: Failure: The menu contains 0 items.
Menu #23: Failure: CreatePopupMenu failed.
Menu #24: Failure: CreatePopupMenu failed.
Menu #25: Failure: CreatePopupMenu failed.
...
Menu #98: Failure: CreatePopupMenu failed.
Menu #99: Failure: CreatePopupMenu failed.
Menu #100: Failure: CreatePopupMenu failed.

Created 22 menus containing a total of 27616 items.

*****

E>a
Menu #1: Failure: The menu contains 4544 items.
Menu #2: Failure: The menu contains 3600 items.
Menu #3: Failure: The menu contains 3464 items.
Menu #4: Failure: The menu contains 3408 items.
Menu #5: Failure: The menu contains 3320 items.
Menu #6: Failure: The menu contains 2848 items.
Menu #7: Failure: The menu contains 2464 items.
Menu #8: Failure: The menu contains 816 items.
Menu #9: Failure: The menu contains 728 items.
Menu #10: Failure: The menu contains 480 items.
Menu #11: Failure: The menu contains 416 items.
Menu #12: Failure: The menu contains 320 items.
Menu #13: Failure: The menu contains 296 items.
Menu #14: Failure: The menu contains 200 items.
Menu #15: Failure: The menu contains 128 items.
Menu #16: Failure: The menu contains 120 items.
Menu #17: Failure: The menu contains 88 items.
Menu #18: Failure: The menu contains 80 items.
Menu #19: Failure: The menu contains 72 items.
Menu #20: Failure: The menu contains 64 items.
Menu #21: Failure: The menu contains 56 items.
Menu #22: Failure: The menu contains 48 items.
Menu #23: Failure: The menu contains 24 items.
Menu #24: Failure: The menu contains 16 items.
Menu #25: Failure: The menu contains 8 items.
Menu #26: Failure: The menu contains 0 items.
Menu #27: Failure: The menu contains 0 items.
Menu #28: Failure: The menu contains 0 items.
Menu #29: Failure: CreatePopupMenu failed.
Menu #30: Failure: CreatePopupMenu failed.
Menu #31: Failure: CreatePopupMenu failed.
...
Menu #98: Failure: CreatePopupMenu failed.
Menu #99: Failure: CreatePopupMenu failed.
Menu #100: Failure: CreatePopupMenu failed.

Created 28 menus containing a total of 27608 items.

*****

So, while there is no problem creating a menu containing thousands of items,
clearly there is a limit on the total number of menu items a single program
can have in WinXP. The limits differ from run to run, which suggests there
may in fact be a system-wide limit. One way to test that hypothesis is to
load several command prompts and run several instances of this program
concurrently. Here's what I got when I did this for three command prompts:

Created 23 menus containing a total of 15616 items.
Created 25 menus containing a total of 11784 items.
Created 12 menus containing a total of 7784 items.

This strongly suggests there is a system-wide limit in WinXP on the total
number of menu items counted over all applications. The sum of the menu
items for my three concurrent instances is around 35,000, which exceeds the
roughly 27,000 I got above when I ran one instance at a time. However, in
the three-instance run, some instances finished earlier than others, and I
observed this pattern in two of them:

*****

E>a
Menu #1: Failure: The menu contains 3824 items.
Menu #2: Failure: The menu contains 1976 items.
Menu #3: Failure: The menu contains 416 items.
Menu #4: Failure: The menu contains 400 items.
Menu #5: Failure: The menu contains 248 items.
Menu #6: Failure: The menu contains 216 items.
Menu #7: Failure: The menu contains 176 items.
Menu #8: Failure: The menu contains 64 items.
Menu #9: Failure: The menu contains 40 items.
Menu #10: Failure: The menu contains 32 items.
Menu #11: Failure: CreatePopupMenu failed.
Menu #12: Failure: CreatePopupMenu failed.
Menu #13: Failure: CreatePopupMenu failed.
...
Menu #41: Failure: CreatePopupMenu failed.
Menu #42: Failure: CreatePopupMenu failed.
Menu #43: Failure: CreatePopupMenu failed.
Menu #44: Failure: The menu contains 1856 items.
Menu #45: Failure: The menu contains 1320 items.
Menu #46: Failure: The menu contains 504 items.
Menu #47: Failure: The menu contains 280 items.
Menu #48: Failure: The menu contains 120 items.
Menu #49: Failure: The menu contains 104 items.
Menu #50: Failure: The menu contains 72 items.
Menu #51: Failure: The menu contains 48 items.
Menu #52: Failure: The menu contains 40 items.
Menu #53: Failure: The menu contains 24 items.
Menu #54: Failure: The menu contains 16 items.
Menu #55: Failure: The menu contains 8 items.
Menu #56: Failure: The menu contains 0 items.
Menu #57: Failure: The menu contains 0 items.
Menu #58: Failure: The menu contains 0 items.
Menu #59: Failure: CreatePopupMenu failed.
Menu #60: Failure: CreatePopupMenu failed.
Menu #61: Failure: CreatePopupMenu failed.
.....
Menu #98: Failure: CreatePopupMenu failed.
Menu #99: Failure: CreatePopupMenu failed.
Menu #100: Failure: CreatePopupMenu failed.

*****

You see how it started to fail, then recovered briefly, and then began to
fail again? I'd guess this is due to another instance exiting before this
one, and the system freeing up its resources, which are made available to
this one. Indeed, if I add up menu items in the "recovery period" in this
instance and the other one in which I observed this pattern, they total 7792
menu items. Subtracting that from the 35,000 total for all three instances
yields roughly the 27,000 items I was getting when running a single
instance. I think this is a valid calculation, because all three saturated
the system and began to fail at about the same time.

There are two things I can't explain. If I can ultimately create around
27,000 menu items in a single instance, why do I get this pattern:

*****

E>a
Menu #1: Success: The menu contains 5000 items.
Menu #2: Success: The menu contains 5000 items.
Menu #3: Success: The menu contains 5000 items.
Menu #4: Failure: The menu contains 4584 items.
Menu #5: Failure: The menu contains 1680 items.
Menu #6: Failure: The menu contains 1560 items.
Menu #7: Failure: The menu contains 1552 items.
Menu #8: Failure: The menu contains 1176 items.
Menu #9: Failure: The menu contains 832 items.
Menu #10: Failure: The menu contains 344 items.
Menu #11: Failure: The menu contains 328 items.
Menu #12: Failure: The menu contains 128 items.
Menu #13: Failure: The menu contains 112 items.
Menu #14: Failure: The menu contains 104 items.
Menu #15: Failure: The menu contains 96 items.
Menu #16: Failure: The menu contains 48 items.
Menu #17: Failure: The menu contains 40 items.
Menu #18: Failure: The menu contains 16 items.
Menu #19: Failure: The menu contains 8 items.
Menu #20: Failure: The menu contains 8 items.
Menu #21: Failure: The menu contains 0 items.
Menu #22: Failure: The menu contains 0 items.
Menu #23: Failure: CreatePopupMenu failed.
Menu #24: Failure: CreatePopupMenu failed.
Menu #25: Failure: CreatePopupMenu failed.
...

*****

If I can get up to 27,000 menu items, why does it start to fail on
individual menus after 15,000 items? That is, why don't I see:

Menu #1: Success: The menu contains 5000 items.
Menu #2: Success: The menu contains 5000 items.
Menu #3: Success: The menu contains 5000 items.
Menu #4: Success: The menu contains 5000 items.
Menu #5: Success: The menu contains 5000 items.
Menu #6: Failure: The menu contains 1560 items.

All I can guess is that the system is very dynamic with this menu item space
pool it appears to have. Also, I have to wonder why CreatePopupMenu began to
fail. Surely I didn't run out of menu handles! It began to fail after
creating a measly 20 or so popups. Maybe the menu handle space is shared
with the menu item space. Maybe the system determined that since I had
exhausted the menu item space, I shouldn't be allowed to create any more
menus.

-- 
Doug Harrison
Microsoft MVP - Visual C++


Relevant Pages

  • Re: 20 Years of JPEG Celebrated With Software Launch
    ... I have nothing against commercial success, ... what the marketing claims. ... So that is why you will fail, and I only point it out to ... Otherwise you will be doomed to failure. ...
    (comp.compression)
  • Return value(s) from functions - any defacto standard?
    ... values that indicate success and fail. ... success, returns 0 for some calls and returns 1 on others. ... suppose if you want to mix in failure mode codes as returns from these ...
    (microsoft.public.vc.mfc)
  • Re: oooppps I forgot (repost from Dawn)
    ... I also forgot to post my meter. ... I will not fail. ... Success comes to those who make it happen, not those who let it happen. ... Failure is the path of least persistence. ...
    (alt.support.stop-smoking)
  • Re: Thread Pre-Emption Question . . .
    ... Not checking for failure of InitializeCriticalSection, ... not checking deallocateor failure is okay because the program will continue to run correctly although it is good practice to check this in debug builds because deallocators usually only fail because of invalid arguments. ... the correct execution of error handling logic (if the access causes unplanned exceptions before data corruption and these unplanned exceptions are caught in the same place as planned exceptions for example). ... This can, and will, corrupt real data in any number of random ways depending on the work done in the writer. ...
    (microsoft.public.win32.programmer.kernel)
  • Re: Thread Pre-Emption Question . . .
    ... Not checking for failure of InitializeCriticalSection, ... not checking deallocateor failure is okay because the program will continue to run correctly although it is good practice to check this in debug builds because deallocators usually only fail because of invalid arguments. ... correct execution of error handling logic (if the access causes unplanned exceptions before data corruption and these unplanned exceptions are caught in the same place as planned exceptions for example). ... This can, and will, corrupt real data in any number of random ways depending on the work done in the writer. ...
    (microsoft.public.win32.programmer.kernel)