0X00000595

ERROR_GLOBAL_ONLY_HOOK 0x595: Why SetWindowsHookEx Fails and How to Fix It

Error 0x595 means a hook can only run globally, but you're trying to set it on one thread. Fix: use a 64-bit DLL, ensure the hook proc is in a DLL, or use a different hook type.

Quick answer

Set the hook with a dwThreadId of 0 (global) instead of a specific thread, or change to a hook type that supports per-thread hooks (like WH_CALLWNDPROC). If you must use a global hook, your hook procedure must live in a DLL, and that DLL must match the bitness of every process you want to hook.

What's actually happening here is that the Windows hook manager in user32.dll enforces a rule: certain hook types are strictly global. WH_KEYBOARD_LL and WH_MOUSE_LL are the usual culprits — they can't be scoped to a single thread. When you call SetWindowsHookEx with a dwThreadId that isn't 0, the system returns ERROR_GLOBAL_ONLY_HOOK (1429 decimal, 0x595). It's not a permission issue or a broken DLL — it's simply that the hook type doesn't allow thread-level scoping. I've seen this trip up plenty of developers when they try to make a low-level keyboard hook apply only to a specific window. The system won't let you, and it tells you exactly why.

Why this happens

Low-level hooks (WH_KEYBOARD_LL, WH_MOUSE_LL) are implemented by injecting a callback into the message loop of the calling thread. That callback needs to see every input event, regardless of which process generated it. If you tried to attach it to one thread, the hook would only receive events when that thread is active — but the system can't guarantee that, and it would break the global input stream. So Microsoft hard-coded a restriction in the SetWindowsHookEx internals. The error is by design, not a bug.

Note that this error also appears when you try to set a global hook from a 32-bit process on a 64-bit system, but that's a different scenario — the error code is the same, but the fix is different. The error message says “only be set globally” which is very specific, so first check your thread ID.

Fix steps

  1. Confirm the hook type. Look at your code. If you're using WH_KEYBOARD_LL (13) or WH_MOUSE_LL (14), those are global-only. Also WH_JOURNALPLAYBACK (1) and WH_JOURNALRECORD (0) are global-only, but they're rarely used anymore.
  2. Set dwThreadId to 0. This tells the system you want a global hook. Example:
    HHOOK hook = SetWindowsHookEx(WH_KEYBOARD_LL, proc, hInstance, 0);
    if (!hook) {
        DWORD err = GetLastError(); // 0x595 if you still pass a thread ID
    }
    That's the immediate fix if you're okay with hooking all threads.
  3. If you need a thread-specific hook, change the hook type. For example, use WH_CALLWNDPROC (4) or WH_GETMESSAGE (3). These support per-thread hooks. But watch out: your hook procedure must be in a DLL, not in your EXE. The system needs to inject the DLL into the target process. If your proc is in the EXE, you'll get a different error (usually 0x57, invalid parameter).
  4. For global low-level hooks, you can keep the proc in your EXE. Low-level hooks are called in the context of the thread that set them, so the proc only needs to be in the module that calls SetWindowsHookEx. That's why you can pass GetModuleHandle(NULL) for hMod. But if you switch to a non-low-level hook, you must move the proc to a DLL.

Alternative fixes if the main one fails

If setting dwThreadId to 0 still returns 0x595, then you're on a 64-bit system and your hook DLL is 32-bit. The system loads a 64-bit version of the DLL into 64-bit processes, and a 32-bit version into 32-bit processes. If you only have one version, you'll get a failure — but the error might be 0x595 or something else. The real fix is to compile two versions of your DLL (x86 and x64) and install both. Or compile your entire app as 64-bit if you only target 64-bit processes.

Another edge case: if you're calling SetWindowsHookEx from a service process, the hook may still fail because the service runs in a different session. That usually gives a different error, but if you see 0x595, double-check that your service isn't passing a thread ID by accident.

Prevention tip

Before writing code, decide whether you truly need a global hook. If you're only tracking keyboard input in a single window, use SetWindowsHookEx with WH_KEYBOARD (2) and the window's thread ID. That works per-thread. Or better, use RegisterHotKey or a plain WM_KEYDOWN handler. Low-level hooks are powerful but expensive — they run on every keystroke system-wide, and they can cause performance issues if your callback is slow. I've seen apps that lag because they did heavy I/O in a hook proc. So keep the callback lean, and only go global when you actually need to see input from all apps.

If you're building a macro recorder, a global keyboard hook is the way to go. Just remember: set dwThreadId to 0, keep the proc in your EXE, and make sure your EXE bitness matches the target OS (or use a helper DLL for non-low-level hooks). That'll get you past 0x595 without surprises.

Always check GetLastError() after SetWindowsHookEx fails. The error code tells you exactly what the system choked on, and 0x595 means it's strictly a thread-scope issue.
Related Errors in Windows Errors
0X80004027 CO_E_CLASS_DISABLED (0X80004027) Fix for Windows 0XC01E05E4 STATUS_GRAPHICS_INVALID_POINTER 0xC01E05E4 Quick Fix 0X0000003C Remote Adapter Not Compatible (0x3C) — Fix It Now 0X000003E6 Fix ERROR_NOACCESS (0X000003E6) Memory Access Failure

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.