1

I have written a program in C# that enables several global hotkeys and based on the hotkey that is pressed, it activates a windows like chrome, firefox, notepad, calculator, etc. After registering the global hotkeys, I have a infinite while-loop that keeps the application alive. After running the program, suddenly the hotkeys stop working. This sometime happens after several hours. After long time testing every piece of my code, I found the issue. The issue is that suddenly the main thread stops working. The program is still alive and in memory. The hotkeys seems to be registered in another thread that is alive and keeps the program running even when the main thread that includes the while-loop is dead.

I then used a backgroundworker and moved the while-loop in the backgroundworker. It happened again, meaning that backgroundworker suddenly stopped while the hotkeys are still registered. This is not my first time using backgroudworker and I never came across something like this that backgroundworker exits by itself.

When this happens, messages like this are appeared in the output windows of Visual Studio:

The thread 0x1b24 has exited with code 0 (0x0)

Is there any time-limit for threads so that they will exit after that? Do you have any suggestion on how this is happening and how I can fix it?

For global hotkeys I use the code listed here:

http://stackoverflow.com/a/3654821/3179989

and this is the rest of my code:

    public static void HotKeyPressed(object sender, HotKeyEventArgs e)
    {
        string PressedHotkey = e.Modifiers.ToString() + " " + e.Key.ToString();
        switch (PressedHotkey)
        {
            case "Alt D1":
                mActivateWindow(mEnumApplications.Chrome);
                break;
            case "Alt D3":
                mActivateWindow(mEnumApplications.CintaNotes);
                break;
            default:
                break;
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        bgWkrHotkey.WorkerSupportsCancellation = true;
        bgWkrHotkey.WorkerReportsProgress = true;
        bgWkrHotkey.RunWorkerAsync();
    }

    private void bgWkrHotkey_DoWork(object sender, DoWorkEventArgs e)
    {
        mHotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Alt);
        mHotKeyManager.RegisterHotKey(Keys.D1, KeyModifiers.Alt);
        mHotKeyManager.RegisterHotKey(Keys.D3, KeyModifiers.Alt);
        mHotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyPressed);

        while (true)
        {
            Thread.Sleep(50);
        }
    }

    //@@@@@@@@@@@@@@@@@@@@ DLL IMPORTS @@@@@@@@@@@@@@@@@@@@
    #region DLL IMPORTS
    [DllImport("User32.dll")]
    private static extern IntPtr SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
    [DllImport("USER32.DLL")]
    static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

    [DllImport("USER32.DLL")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("USER32.DLL")]
    static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    static extern IntPtr GetShellWindow();
    #endregion DLL IMPORTS

    public static IDictionary<IntPtr, string> mGetOpenWindows()
    {
        IntPtr ipShellWindow = GetShellWindow();
        Dictionary<IntPtr, string> ipWindows = new Dictionary<IntPtr, string>();

        EnumWindows(delegate(IntPtr hWnd, int lParam)
        {
            if (hWnd == ipShellWindow) return true;
            //if (!IsWindowVisible(hWnd)) return true;

            int lLength = GetWindowTextLength(hWnd);
            if (lLength == 0) return true;

            StringBuilder lBuilder = new StringBuilder(lLength);
            GetWindowText(hWnd, lBuilder, lLength + 1);

            ipWindows[hWnd] = lBuilder.ToString();
            return true;

        }, 0);

        return ipWindows;
    }

    public static string mGetActiveWindowTitle()
    {
        const int nChars = 256;
        StringBuilder Buff = new StringBuilder(nChars);
        IntPtr handle = GetForegroundWindow();

        if (GetWindowText(handle, Buff, nChars) > 0)
        {
            return Buff.ToString();
        }
        return "";
    }


    public static bool mActivateWindow(IntPtr ipHandle, string strWindowTitle)
    {
        StringBuilder Buff = new StringBuilder(256);
        SetForegroundWindow(ipHandle);

        Stopwatch swTimeout = new Stopwatch();
        swTimeout.Start();
        while (swTimeout.Elapsed < TimeSpan.FromSeconds(2))
        {
            ipHandle = GetForegroundWindow();
            if ((GetWindowText(ipHandle, Buff, 256) > 0) && (Buff.ToString().ToLower().Contains(strWindowTitle.ToLower())))
                return true;
            else
            {
                SetForegroundWindow(ipHandle);
                Thread.Sleep(50);
            }
        }
        swTimeout.Stop();
        return false;
    }

    public static bool mActivateWindow(mEnumApplications enumApp)
    {
        string strWindowTitle = "";
        switch (enumApp)
        {
            case mEnumApplications.Chrome:
                strWindowTitle = "Google Chrome";
                break;
            case mEnumApplications.CintaNotes:
                strWindowTitle = "CintaNotes";
                break;
            default:
                break;
        }

        IntPtr ipHandle = IntPtr.Zero;
        string strExactTitle = "";
        StringBuilder Buff = new StringBuilder(256);
        foreach (KeyValuePair<IntPtr, string> ipWindow in mGetOpenWindows())
        {
            ipHandle = ipWindow.Key;
            strExactTitle = ipWindow.Value;

            if (strExactTitle.ToLower().Contains(strWindowTitle.ToLower()))
                if (mActivateWindow(ipHandle, strWindowTitle))
                    return true;

        }
        return false;
    }

    public enum mEnumApplications
    {
        Null,
        Chrome,
        CintaNotes,
    };

I appreciate any help. Thanks

NESHOM
  • 899
  • 16
  • 46
  • 1
    You had an unexplained crash in your main thread, so you moved the hot keys to a `BackgroundWorker`? Seems like you should have diagnosed that main thread crash. Moving to the `BackgroundWorker` just doubled (at least) the problem. That said, I'd suspect that something in your hot key pressed handler is throwing an exception. Put a try/catch around it and log any exception you get. – Jim Mischel Nov 16 '14 at 19:39
  • In addition, I'd look closely at your `EnumWindows` callback. The allocation of memory for your `StringBuilder` is suspect, especially since you're allocating it to `lLength`, but then passing `lLength+1` to `GetWindowText`. That might very well try to overwrite memory you don't own, and cause the thread to crash. `GetWindowTextLength` isn't specific about whether the returned value includes the null terminator. Quite possibly adding 1 to the return value and using the result to initialize your `StringBuilder` will solve the problem. – Jim Mischel Nov 16 '14 at 19:47
  • Thanks for your comments. That part of the code isn't actually my own code and I just copied from internet (http://blog.tcx.be/2006/05/getting-list-of-all-open-windows.html). Sorry I should have cited this in my main post. Could you please change the code and send it as an answer as I am not exactly sure about what you mean? Thanks – NESHOM Nov 16 '14 at 19:52
  • In the `EnumWindows` call, change this `new StringBuilder(lLength);` to `new StringBuilder(lLength+1);` – Jim Mischel Nov 16 '14 at 20:03
  • I changed that. Now it is running. I will reply once I make sure that the problem is solved or not. Thank you. – NESHOM Nov 16 '14 at 20:42
  • @JimMischel - It happened again. do you have any other suggestion? The thing is the hotkeys are still active, the main/backgroundworker thread is dead (The thread 0xf00 has exited with code 259 (0x103).). So presumably there shouldn't be something wrong with the hotkey handler. – NESHOM Nov 16 '14 at 21:12
  • My only suggestions are: 1) Get rid of the extra thread. It's just adding another variable. 2) Check every error code and use try/catch to capture and log all exceptions. As it stands, you have no idea where it's failing. You have to identify *where* it's failing first. Making changes to the code to fix a problem that you can't identify isn't likely to get you anywhere. – Jim Mischel Nov 16 '14 at 21:17
  • @JimMischel - I think I found the problem. Something is wrong with 'int lLength = GetWindowTextLength(hWnd);' as when I am in debug mode using F11, the highlighted line (current running line) is disappeared after 'GetWindowTextLength'. I changed it to 'int lLength = 0; try { lLength = GetWindowTextLength(hWnd); } catch (Exception e) { MessageBox.Show(e.ToString()); }', but no exception is caught! Do you see anything wrong with this line of code? – NESHOM Nov 16 '14 at 22:27
  • [GetWindowTextLength](http://msdn.microsoft.com/en-us/library/windows/desktop/ms633521(v=vs.85).aspx) will not throw an exception. If it returns 0, there is an error. Change your `DllImport` for `GetWindowTextLength` to `[DllImport("USER32.DLL", SetLastError=true)]`. And if `GetWindowTextLength` return 0, then call [Marshal.GetLastWin32Error](http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.getlastwin32error(v=vs.100).aspx) to get the error code. – Jim Mischel Nov 16 '14 at 22:33
  • @JimMischel - thank you for your prompt replies. It returns error `87 (ERROR_INVALID_PARAMETER)`. Do you have any idea on what is causing this error? – NESHOM Nov 16 '14 at 22:41
  • It also sometime returns `error code 183`(`ERROR_ALREADY_EXISTS 183 (0xB7) Cannot create a file when that file already exists.`). Any idea? – NESHOM Nov 16 '14 at 23:00
  • @JimMischel - I noticed that if I initiate `mActivateWindow(mEnumApplications.Chrome);` using hotkeys that I defined, then I will get lots of 87 error code from `GetWindowTextLength` and ultimately this will cause the thread to exit. However if I manually initiate `mActivateWindow(mEnumApplications.Chrome);` (e.g. using a button), I will still get lots of 87 errors, but this does not make the thread to exit and the `mGetOpenWindows` method will end properly and the code continues. – NESHOM Nov 16 '14 at 23:23

1 Answers1

1

Reviewing the code, the error probably isn't in how you're calling GetWindowTextLength. You have:

int lLength = GetWindowTextLength(hWnd);
if (lLength == 0) return true;

So if GetWindowTextLength has an error, your function just returns.

There is, however, an error in how you're allocating the StringBuilder. If you look at the comments on the GetWindowTextLength page, you'll see that the value returned does not include the null terminator. So when you allocate your StringBuilder, you're getting it one character too small. Your code should be:

StringBuilder lBuilder = new StringBuilder(lLength+1);  // <-- changed to lLength+1
GetWindowText(hWnd, lBuilder, lLength + 1);

If you don't make that change, then it's possible that a call to GetWindowText will overwrite your buffer, and that will cause a crash.

A potential problem is that your managed prototypes are okay for 32 bit, but not for 64 bit. For example, you have:

delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

That's okay for 32-bit because an lParam is 32 bits. But in 64-bit, lParam is 64 bits. That prototype should be:

delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

Similar thing with your EnumWindows prototype. It should be:

[DllImport("USER32.DLL")]
static extern bool EnumWindows(EnumWindowsProc enumFunc, IntPtr lParam);

And when you call it, specify IntPtr.Zero for the parameter, rather than 0.

It's curious that you can't trap the error. If you've fixed the things that I pointed out above and you're still getting the error, I would suggest that you're looking in the wrong place.

In particular, there is absolutely no reason that you need a separate thread for the hot keys. You should be able to define the keys in the main program, and as long as the main program is running, the hot keys will work. Adding a thread just confuses the issue.

Beyond that, I can't be of much more help until you track down exactly what's causing the problem. You need to check every return value from unmanaged function calls. You also should consider adding some logging, and log every action. That way you can more easily determine where the error is occurring.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Many thank Jim for your informative answer. I modified the code based on your comments, however it still crashes whenever I press the hotkey (e.g. Alt+1). I haven't added any thread. I removed the backgroundworker as you recommended. The code that I am running is the simplified version of my main code and I was expecting to see better results out of it, but it seems to get worse. I uploaded the solution folder into my dropbox and I am wondering if you can have a quick look at it : `https://db.tt/3FFRBrpL`. Thanks you. – NESHOM Nov 17 '14 at 00:57
  • I forgot to mention that `Button1` activates the hotkeys. Once they are activated, pressing `Alt+1` makes all of the hotkeys inactive (because of the crash)! `Button3` just executes the `ActiveWindows()` method and it works without any issue. – NESHOM Nov 17 '14 at 01:03