Question

I'm trying to gather the text of a menu item using GetMenuItemInfo API.

This is the code I'm using:

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool GetMenuItemInfo(IntPtr hMenu, uint uItem, bool fByPosition, ref MENUITEMINFO lpmii);

[StructLayout(LayoutKind.Sequential)]
public struct MENUITEMINFO 
{
    public uint cbSize;
    public uint fMask;
    public uint fType;
    public uint fState;
    public uint wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public IntPtr dwItemData;
    public String dwTypeData;
    public uint cch;
    public IntPtr hbmpItem;

    // Return the size of the structure
    public static uint sizeOf
    {
        get { return (uint)Marshal.SizeOf(typeof(MENUITEMINFO)); }
    }
}

MENUITEMINFO mif = new MENUITEMINFO();
mif.cbSize = MENUITEMINFO.sizeOf;
mif.fMask = MIIM_STRING; 
mif.fType = MFT_STRING;
mif.dwTypeData = null;
bool res = Win32.GetMenuItemInfo(hMenu, (uint)0, true, ref mif); //To load cch into memory

mif.cch += 1;
mif.fMask = MIIM_STRING;
mif.fType = MFT_STRING;
mif.dwTypeData = new String(' ', (Int32)(mif.cch));

res = Win32.GetMenuItemInfo(hMenu, (uint)i, true, ref mif); //To fill dwTypeData

Unfortunately after those lines dwTypeData results a string with only 1 character instead of mif.cch characters. Note: this character is the first one of the menu item!

It seems that there's some misalignment in the marshaling of data when returning MENUITEMINFO structure, isn.t it?

Please let me know how to solve such issue!

Best

cghersi

Was it helpful?

Solution

Your header translation is pretty good, but you've not got dwTypeData quite right. That cannot be marshalled from caller to callee using string. You need to do that with manual marshalling like this.

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, MENUITEMINFO lpmii);

[StructLayout(LayoutKind.Sequential)]
public class MENUITEMINFO 
{
    public int cbSize;
    public uint fMask;
    public uint fType;
    public uint fState;
    public uint wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public IntPtr dwItemData;
    public IntPtr dwTypeData;
    public uint cch;
    public IntPtr hbmpItem;

    public MENUITEMINFO()
    {
        cbSize = Marshal.SizeOf(typeof(MENUITEMINFO));
    }
}

....

MENUITEMINFO mif = new MENUITEMINFO();
mif.fMask = MIIM_STRING; 
mif.fType = MFT_STRING;

mif.dwTypeData = IntPtr.Zero;
bool res = GetMenuItemInfo(hMenu, 0, true, mif);
if (!res)
    throw new Win32Exception();
mif.cch++;
mif.dwTypeData = Marshal.AllocHGlobal((IntPtr)(mif.cch*2));
try
{
    res = GetMenuItemInfo(hMenu, 0, true, mif);
    if (!res)
        throw new Win32Exception();
    string caption = Marshal.PtrToStringUni(mif.dwTypeData);
}
finally
{
    Marshal.FreeHGlobal(mif.dwTypeData);
}

OTHER TIPS

Update 2

Code found in System.Windows.Forms.MenuItem using reflection. Seems that they use string also.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MENUITEMINFO_T
{
    public int cbSize = Marshal.SizeOf(typeof(NativeMethods.MENUITEMINFO_T));
    public int fMask;
    public int fType;
    public int fState;
    public int wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public IntPtr dwItemData;
    public string dwTypeData;
    public int cch;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool GetMenuItemInfo(HandleRef hMenu, int uItem, bool fByPosition, [In, Out] MENUITEMINFO_T lpmii);

  ................

  MENUITEMINFO_T menuiteminfo_t;
  menuiteminfo_t = new MENUITEMINFO_T();
  menuiteminfo_t.fMask = MIIM_STRING;
  menuiteminfo_t.dwTypeData = new string('\0', 256);
  menuiteminfo_t.cch = menuiteminfo_t.dwTypeData.Length - 1;
  bool result = GetMenuItemInfo(new HandleRef(null, hMenu), 0, true, menuiteminfo_t);

Update 1

As @Remy Lebeau suggested, there was a differences between String marshaling vs char[] marshaling. After I added CharSet=CharSet.Auto attribute to the structure everything was working fine using your code.

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
    public struct MENUITEMINFO
    {
        public uint cbSize;
        public uint fMask;
        public uint fType;
        public uint fState;
        public uint wID;
        public IntPtr hSubMenu;
        public IntPtr hbmpChecked;
        public IntPtr hbmpUnchecked;
        public IntPtr dwItemData;
        public string dwTypeData;
        public uint cch;
        public IntPtr hbmpItem;

        // return the size of the structure
        public static uint sizeOf
        {
            get { return (uint)Marshal.SizeOf(typeof(MENUITEMINFO)); }
        }
    }


        IntPtr hWnd = FindWindow(null, "untitled - notepad");
        IntPtr hMenu = GetMenu(hWnd);


        MENUITEMINFO mif = new MENUITEMINFO();
        uint MIIM_STRING = 0x00000040;
        uint MFT_STRING = 0x00000000;
        mif.cbSize = MENUITEMINFO.sizeOf;//(uint)Marshal.SizeOf(typeof(MENUITEMINFO));
        mif.fMask = MIIM_STRING;
        mif.fType = MFT_STRING;
        mif.dwTypeData = null;
        //IntPtr hMenu = menuStrip1.Handle;



        //1st call to get the text length into (cch)
        bool res = GetMenuItemInfo(hMenu, (uint)0, true, ref mif); //To load cch into memory
        if (res)
        {
            mif.cch += 1;
            //Set the length of the buffer to cch + 1
            mif.dwTypeData = new string(' ', (int)(mif.cch));
            res = GetMenuItemInfo(hMenu, (uint)0, true, ref mif); //To fill dwTypeData

        }

Check this link for more details about Default Marshaling for Strings

OLD:

You need to initialize dwTypeData and cch data members.

Get Menu items string example (working with the new MENUITEMINFO structure):

List<string> ls = new List<string>();
IntPtr hMenu = Win32.GetMenu(hWnd);

if (hMenu.ToInt32() != 0)
{
    for (int i = Win32.GetMenuItemCount(hMenu); i >= 0; i--)
    {
        uint MIIM_STRING = 0x00000040;
        uint MFT_STRING  = 0x00000000;
        Win32.MENUITEMINFO mif = new Win32.MENUITEMINFO();
        mif.cbSize = (uint)Marshal.SizeOf(typeof(Win32.MENUITEMINFO));
        mif.fMask = MIIM_STRING;
        mif.fType = MFT_STRING;
        mif.cch = 256;
        mif.dwTypeData = new string(' ', (int)(mif.cch));

        bool a = Win32.GetMenuItemInfo(hMenu, 0, true, ref mif);
        ls.Add(mif.dwTypeData);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top