3

TL;DR: Did GetWindowText win32 api change behavior on windows 10?

I have some code that loops through all windows on the desktop to find a window where the title contains some text.

When this code hits a window named "" with class "URL Moniker Notification Window" it hangs on GetWindowText.

GetWindowText is trying to send a message, my guess is WM_GETTEXT.

This window is part of the exe "SearchUI.exe", the process is suspended and can't process messages.

When reading: https://blogs.msdn.microsoft.com/oldnewthing/20030821-00/?p=42833/ according to rule 2, this should not happen.

This code has been working fine for years. (win 7, 8, 8.1)

So did GetWindowText change behavior in Windows 10?

Update: The code in question.

public static int HwndGet(string partialTitle, string klassenavn)
{
  partialTitle = partialTitle ?? "";
  var cTitleTemp = new StringBuilder(255);
  var hWndTemp = FindWindowEx((IntPtr)0, (IntPtr)0, null, null);
  var nypartialTitle = partialTitle.ToUpper();
  while (hWndTemp != (IntPtr)0)
  {
    GetWindowText(hWndTemp, cTitleTemp, cTitleTemp.Capacity);
    string sTitleTemp = cTitleTemp.ToString();
    sTitleTemp = sTitleTemp.ToUpper();

    if (sTitleTemp.StartsWith(nypartialTitle, StringComparison.CurrentCultureIgnoreCase))
    {
      var className = new StringBuilder(255);
      GetClassName(hWndTemp, className, 255);
      //sTitleTemp: " + sTitleTemp + " ClassName: " + ClassName);

      if (className.ToString().StartsWith(klassenavn, StringComparison.CurrentCultureIgnoreCase))
      {
        return (int)hWndTemp;
      }
    }

    hWndTemp = GetWindow(hWndTemp, GwHwndnext);
  }
  return 0; // does not find the window
}

Stack trace:

Stack trace

Nickolai Nielsen
  • 932
  • 6
  • 19

1 Answers1

2

The code you are using isn't "safe". There is no guarantee that the order of the windows won't change between calls to FindWindowsEx and GetWindow(GwHwndnext). For this reason there is another API, EnumWindows, that is "safe". You could try with it.

Here there is a sample program (based on the one found here).

public static class WndSearcher
{
    public static IntPtr SearchForWindow(string wndclass, string title)
    {
        var sd = new SearchData { Wndclass = wndclass, Title = title };

        EnumWindows(sd.EnumWindowsProc, IntPtr.Zero);
        return sd.hWndFound;
    }

    private class SearchData
    {
        // You can put any dicks or Doms in here...
        public string Wndclass;
        public string Title;

        public IntPtr hWndFound;

        public bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam)
        {
            // Check classname and title 
            var sb = new StringBuilder(1024);
            int res = GetClassName(hWnd, sb, sb.Capacity);

            if (res == 0)
            {
                throw new Win32Exception();
            }

            if (sb.ToString().StartsWith(Wndclass, StringComparison.CurrentCultureIgnoreCase))
            {
                sb.Clear();

                res = GetWindowText(hWnd, sb, sb.Capacity);

                if (res == 0)
                {
                    int error = Marshal.GetLastWin32Error();

                    if (error != 0) 
                    {
                        throw new Win32Exception(error);
                    }
                }

                if (sb.ToString().StartsWith(Title, StringComparison.CurrentCultureIgnoreCase))
                {
                    hWndFound = hWnd;
                    // Found the wnd, halt enumeration
                    return false;    
                }
            }

            return true;
        }
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
}

and then use it like

IntPtr ptr = WndSearcher.SearchForWindow("classname", "windowname");
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Nice code, my solution is also to call GetClassName first, so I will never hit the suspended process. But it is not an answer to my question. – Nickolai Nielsen Mar 09 '16 at 15:17
  • GetWindowText can return 0 if there is no title of a window, so this code will always throw an exception. Replace **if (res == 0)** after GetWindowText with **var errorNum = Marshal.GetLastWin32Error(); if (res == 0 && errorNum != 0)** – Nickolai Nielsen Mar 10 '16 at 20:51
  • @NickolaiNielsen Right... I've done it in a little different way. – xanatos Mar 11 '16 at 13:12