Oliver Spryn
Oliver Spryn

Reputation: 17348

C# Win32 Interop Crashes when Enumerating Window Handles

I have a C# wrapper for some Win32 operations involving window handles, but I am experiencing an unexpected crash, with no details, when I call a Win32 function.

Interestingly, this whole code sample works fine when the class is constructed (at application init time), but later fails when the same buildCache() method is called.

Here is the relevant code:

public delegate bool CallBack(int hWnd, int lParam);

public class Win32Interop {
    private Dictionary<int, string> windowCache = new Dictionary<int, string>();

    public Win32Interop() {
        buildCache();
    }

    public void buildCache() {
        windowCache.Clear();

        CallBack hWndCacher = new CallBack(saveHWndHandler);
        EnumWindows(hWndCacher, 0);
    }

    public void doThings(string title, uint message, bool rebuildCache = false) {
    //Use the window title to get its handle
        int hWnd = titleToHWnd(title, rebuildCache);
        SendMessage(hWnd, message, 0, 0);
    }

    private bool saveHWndHandler(int hWnd, int lParam) {
        if(IsWindow(hWnd) != 0) {       / ***** CRASHES HERE ***** /
            int length = GetWindowTextLength(hWnd);
            StringBuilder title = new StringBuilder(length + 1);

            GetWindowText(hWnd, title, title.Capacity);
            string formatted = title.ToString().Trim();

            windowCache.Add(hWnd, formatted);
        }

        return true;
    }

    private int titleToHWnd(string title, bool rebuildCache = false) {
        if(rebuildCache)
            buildCache();

        if(windowCache.ContainsValue(title)) {
            return windowCache.FirstOrDefault(x => x.Value.Contains(title)).Key;
        } else {
            throw new KeyNotFoundException(string.Format("\"{0}\" is not a window title which is available in the cache.", title));
        }
    }

    #region Win32 API Functions
    [DllImport("user32.dll")]
    private static extern int EnumWindows(CallBack lpEnumFunc, int lParam);

    [DllImport("user32.dll")]
    private static extern int GetWindowText(int hWnd, StringBuilder lpString, int maxCount);

    [DllImport("user32.dll")]
    private static extern int GetWindowTextLength(int hWnd);

    [DllImport("user32.dll")]
    private static extern int IsWindow(int hWnd);

    [DllImport("user32.lib")]
    private static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
    #endregion
}

Inside of the saveHWndHandler() method, I've marked the line were the debugger shows execution stops. Interestingly, there are usually ~300 window handles returned by EnumWindows(), and it always crashes on iteration number 45 or 46. The window handle it crashes on is a reasonable value, such as 12345.

According to MSDN, IsWindow() should return 0, if a window was not associated with the handle, not crash the thread.

Does anyone know why this is happening? There isn't a thrown exception, or any details in the Windows event log.

Thank you.

For those who don't want to figure out the buildCache() process works: (1.) When buildCache() is called, the dictionary of <HWnd, Title> values is cleared. (2.) The Win32 function EnumWindows() is called, which calls the saveHWndHandler() method for each window handle. (3.) saveHWndHandler() will check if the current window handle still exists, the call another Win32 to function to get the window title from the handle. (4.) The title and window handle are added to the dictionary.

Upvotes: 0

Views: 885

Answers (1)

Adam Maras
Adam Maras

Reputation: 26843

I can't reproduce your issue, but a likely problem is that all of your P/Invoke signatures are wrong. HWND, LPARAM, and WPARAM data types need to at least be mapped to IntPtr in P/Invoke code.

private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

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

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

[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindow(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam,
    IntPtr lParam);

You'll need to change your corresponding instance method signatures and usages to match these correct signatures.

Upvotes: 3

Related Questions