This is required to avoid silencing (potentially fatal) exceptions from timer procedures.
-- v5: win32u: Ignore unhandled info index in NtUserSetObjectInformation. win32u/tests: Add tests for NtUserSetObjectInformation. user32: Implement UOI_TIMERPROC_EXCEPTION_SUPPRESSION. user32/tests: Add tests for UOI_TIMERPROC_EXCEPTION_SUPPRESSION. include: Add definition for UOI_TIMERPROC_EXCEPTION_SUPPRESSION user32/tests: Make test_unicode_wm_char robust against superfluous messages.
From: Jinoh Kang jinoh.kang.kr@gmail.com
Commit e445303ab45 (user32/tests: Make a few more messages optional., 2014-03-20) modified test_unicode_wm_char so that it skips non-essential messages (e.g. WM_DWMNCRENDERINGCHANGED) from GetMessageW.
Extend this for messages from GetMessageA as well.
Also, handle the case where no messages other than WM_QUIT are received at all.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/user32/tests/msg.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index c931ca6a983..ef448d3a224 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -16339,6 +16339,16 @@ static void test_dbcs_wm_char(void) DestroyWindow(hwnd2); }
+static BOOL get_next_msg( BOOL (*WINAPI getmessage)(MSG *, HWND, UINT, UINT), + MSG *msg, HWND hwnd ) +{ + while ((*getmessage)( msg, hwnd, 0, 0 )) + { + if (!ignore_message( msg->message )) return TRUE; + } + return FALSE; +} + static void test_unicode_wm_char(void) { HWND hwnd; @@ -16369,11 +16379,7 @@ static void test_unicode_wm_char(void)
PostMessageW( hwnd, WM_CHAR, 0x3b1, 0 );
- while (GetMessageW( &msg, hwnd, 0, 0 )) - { - if (!ignore_message( msg.message )) break; - } - + ok( get_next_msg( GetMessageW, &msg, hwnd ), "expected a recongized message\n" ); ok( msg.hwnd == hwnd, "unexpected hwnd %p\n", msg.hwnd ); ok( msg.message == WM_CHAR, "unexpected message %x\n", msg.message ); ok( msg.wParam == 0x3b1, "bad wparam %Ix\n", msg.wParam ); @@ -16393,7 +16399,7 @@ static void test_unicode_wm_char(void) /* greek alpha -> 'a' in cp1252 */ PostMessageW( hwnd, WM_CHAR, 0x3b1, 0 );
- ok( GetMessageA( &msg, hwnd, 0, 0 ), "no message\n" ); + ok( get_next_msg( GetMessageA, &msg, hwnd ), "expected a recognized message\n" ); ok( msg.hwnd == hwnd, "unexpected hwnd %p\n", msg.hwnd ); ok( msg.message == WM_CHAR, "unexpected message %x\n", msg.message ); ok( msg.wParam == 0x61, "bad wparam %Ix\n", msg.wParam ); @@ -16414,7 +16420,7 @@ static void test_unicode_wm_char(void) /* greek alpha -> 0xe1 in cp1253 */ PostMessageW( hwnd, WM_CHAR, 0x3b1, 0 );
- ok( GetMessageA( &msg, hwnd, 0, 0 ), "no message\n" ); + ok( get_next_msg( GetMessageA, &msg, hwnd ), "expected a recognized message\n" ); ok( msg.hwnd == hwnd, "unexpected hwnd %p\n", msg.hwnd ); ok( msg.message == WM_CHAR, "unexpected message %x\n", msg.message ); ok( msg.wParam == 0xe1, "bad wparam %Ix\n", msg.wParam );
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- include/winuser.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/winuser.h b/include/winuser.h index 453f561e62f..666deeb6cbe 100644 --- a/include/winuser.h +++ b/include/winuser.h @@ -99,6 +99,7 @@ typedef void* HPOWERNOTIFY; #define UOI_NAME 2 #define UOI_TYPE 3 #define UOI_USER_SID 4 +#define UOI_TIMERPROC_EXCEPTION_SUPPRESSION 7
#define WSF_VISIBLE 1 #define DF_ALLOWOTHERACCOUNTHOOK 1
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/user32/tests/msg.c | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index ef448d3a224..cae03be9b85 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -11558,11 +11558,36 @@ static void CALLBACK callback_count(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWOR count++; }
+enum timer_exception_phase { + TIMER_EXCEPTION_INITIAL, + TIMER_EXCEPTION_RAISED, + TIMER_EXCEPTION_CONTINUE, + TIMER_EXCEPTION_CONTINUE_OK, +}; + static DWORD exception; +static enum timer_exception_phase timer_exc_phase; +static BOOL tproc_exc_no_suppress; static void CALLBACK callback_exception(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { + if (tproc_exc_no_suppress) + { + BOOL ret, value; + + value = FALSE; + ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, + &value, sizeof(value)); + todo_wine + ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); + tproc_exc_no_suppress = FALSE; + } + count++; + timer_exc_phase = TIMER_EXCEPTION_RAISED; RaiseException(exception, 0, 0, NULL); + ok(timer_exc_phase == TIMER_EXCEPTION_CONTINUE, + "expected phase %d, got %d\n", TIMER_EXCEPTION_CONTINUE, timer_exc_phase); + timer_exc_phase = TIMER_EXCEPTION_CONTINUE_OK; }
static DWORD WINAPI timer_thread_proc(LPVOID x) @@ -11723,10 +11748,41 @@ static void test_timers_no_wnd(void) while (i > 0) KillTimer(NULL, ids[--i]); }
+static LONG CALLBACK timer_exception_handler(EXCEPTION_POINTERS *eptr) +{ + if (timer_exc_phase == TIMER_EXCEPTION_RAISED && + eptr->ExceptionRecord->ExceptionCode == exception && + eptr->ExceptionRecord->NumberParameters == 0) + { + if (eptr->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { +#if defined(__i386__) + if ((ULONG_PTR)eptr->ExceptionRecord->ExceptionAddress == eptr->ContextRecord->Eip + 1) + eptr->ContextRecord->Eip++; /* cancel EIP rewinding */ +#elif defined(__x86_64__) + if ((ULONG_PTR)eptr->ExceptionRecord->ExceptionAddress == eptr->ContextRecord->Rip + 1) + eptr->ContextRecord->Rip++; /* cancel RIP rewinding */ +#endif + } + timer_exc_phase = TIMER_EXCEPTION_CONTINUE; + return EXCEPTION_CONTINUE_EXECUTION; + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +static void dispatch_message_ansi_handle_exception(const MSG *msg, PVECTORED_EXCEPTION_HANDLER handler) +{ + void *cookie = AddVectoredExceptionHandler(TRUE, handler); + DispatchMessageA(msg); + RemoveVectoredExceptionHandler(cookie); +} + static void test_timers_exception(DWORD code) { UINT_PTR id; MSG msg; + BOOL ret, value;
exception = code; id = SetTimer(NULL, 0, 1000, callback_exception); @@ -11738,8 +11794,55 @@ static void test_timers_exception(DWORD code) msg.lParam = (LPARAM)callback_exception;
count = 0; + timer_exc_phase = TIMER_EXCEPTION_INITIAL; DispatchMessageA(&msg); ok(count == 1, "did not get one count as expected (%i).\n", count); + ok(timer_exc_phase == TIMER_EXCEPTION_RAISED, + "expected phase %d, got %d\n", TIMER_EXCEPTION_RAISED, timer_exc_phase); + + value = FALSE; + ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, + &value, sizeof(value)); + if (!ret && GetLastError() == ERROR_INVALID_FUNCTION) + { + win_skip("UOI_TIMERPROC_EXCEPTION_SUPPRESSION not supported on this platform\n"); + } + else + { + todo_wine + ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); + + count = 0; + timer_exc_phase = TIMER_EXCEPTION_INITIAL; + dispatch_message_ansi_handle_exception(&msg, timer_exception_handler); + ok(count == 1, "expected count to be 1, got %d\n", count); + todo_wine + ok(timer_exc_phase == TIMER_EXCEPTION_CONTINUE_OK || + broken(timer_exc_phase == TIMER_EXCEPTION_RAISED) /* < win10 1507 */, + "expected phase %d, got %d\n", TIMER_EXCEPTION_CONTINUE_OK, timer_exc_phase); + + value = TRUE; + ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, + &value, sizeof(value)); + todo_wine + ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); + + tproc_exc_no_suppress = TRUE; + count = 0; + timer_exc_phase = TIMER_EXCEPTION_INITIAL; + dispatch_message_ansi_handle_exception(&msg, timer_exception_handler); + ok(count == 1, "expected count to be 1, got %d\n", count); + todo_wine + ok(timer_exc_phase == TIMER_EXCEPTION_CONTINUE_OK || + broken(timer_exc_phase == TIMER_EXCEPTION_RAISED) /* < win10 1507 */, + "expected phase %d, got %d\n", TIMER_EXCEPTION_CONTINUE_OK, timer_exc_phase); + + value = TRUE; + ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, + &value, sizeof(value)); + todo_wine + ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); + }
KillTimer(NULL, id); }
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/user32/message.c | 11 ++++++++++- dlls/user32/tests/msg.c | 6 ------ dlls/user32/user32.spec | 2 +- dlls/user32/user_main.c | 1 + dlls/user32/user_private.h | 1 + dlls/user32/winstation.c | 23 ++++++++++++++++++++++- 6 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/dlls/user32/message.c b/dlls/user32/message.c index 4016c95539a..54ee5317eed 100644 --- a/dlls/user32/message.c +++ b/dlls/user32/message.c @@ -861,6 +861,15 @@ static LRESULT dispatch_message( const MSG *msg, BOOL ansi ) }
+static LONG WINAPI timerproc_exception_filter(EXCEPTION_POINTERS *eptr) +{ + if (suppress_timerproc_exception) + return EXCEPTION_EXECUTE_HANDLER; + + return EXCEPTION_CONTINUE_SEARCH; +} + + /*********************************************************************** * DispatchMessageA (USER32.@) * @@ -878,7 +887,7 @@ LRESULT WINAPI DECLSPEC_HOTPATCH DispatchMessageA( const MSG* msg ) retval = CallWindowProcA( (WNDPROC)msg->lParam, msg->hwnd, msg->message, msg->wParam, GetTickCount() ); } - __EXCEPT_ALL + __EXCEPT(timerproc_exception_filter) { retval = 0; } diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index cae03be9b85..7365bd1f3fb 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -11577,7 +11577,6 @@ static void CALLBACK callback_exception(HWND hwnd, UINT uMsg, UINT_PTR idEvent, value = FALSE; ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, &value, sizeof(value)); - todo_wine ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); tproc_exc_no_suppress = FALSE; } @@ -11809,14 +11808,12 @@ static void test_timers_exception(DWORD code) } else { - todo_wine ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError());
count = 0; timer_exc_phase = TIMER_EXCEPTION_INITIAL; dispatch_message_ansi_handle_exception(&msg, timer_exception_handler); ok(count == 1, "expected count to be 1, got %d\n", count); - todo_wine ok(timer_exc_phase == TIMER_EXCEPTION_CONTINUE_OK || broken(timer_exc_phase == TIMER_EXCEPTION_RAISED) /* < win10 1507 */, "expected phase %d, got %d\n", TIMER_EXCEPTION_CONTINUE_OK, timer_exc_phase); @@ -11824,7 +11821,6 @@ static void test_timers_exception(DWORD code) value = TRUE; ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, &value, sizeof(value)); - todo_wine ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError());
tproc_exc_no_suppress = TRUE; @@ -11832,7 +11828,6 @@ static void test_timers_exception(DWORD code) timer_exc_phase = TIMER_EXCEPTION_INITIAL; dispatch_message_ansi_handle_exception(&msg, timer_exception_handler); ok(count == 1, "expected count to be 1, got %d\n", count); - todo_wine ok(timer_exc_phase == TIMER_EXCEPTION_CONTINUE_OK || broken(timer_exc_phase == TIMER_EXCEPTION_RAISED) /* < win10 1507 */, "expected phase %d, got %d\n", TIMER_EXCEPTION_CONTINUE_OK, timer_exc_phase); @@ -11840,7 +11835,6 @@ static void test_timers_exception(DWORD code) value = TRUE; ret = SetUserObjectInformationW(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, &value, sizeof(value)); - todo_wine ok(ret, "SetUserObjectInformationW error %lu\n", GetLastError()); }
diff --git a/dlls/user32/user32.spec b/dlls/user32/user32.spec index 96e6e1a7d6b..6a605ae4216 100644 --- a/dlls/user32/user32.spec +++ b/dlls/user32/user32.spec @@ -721,7 +721,7 @@ @ stdcall SetThreadDpiAwarenessContext(ptr) @ stdcall SetTimer(long long long ptr) @ stdcall SetUserObjectInformationA(long long ptr long) -@ stdcall SetUserObjectInformationW(long long ptr long) NtUserSetObjectInformation +@ stdcall SetUserObjectInformationW(long long ptr long) @ stdcall SetUserObjectSecurity(long ptr ptr) @ stdcall SetWinEventHook(long long long ptr long long long) @ stdcall SetWindowCompositionAttribute(ptr ptr) diff --git a/dlls/user32/user_main.c b/dlls/user32/user_main.c index ef2b94ba698..f375462487b 100644 --- a/dlls/user32/user_main.c +++ b/dlls/user32/user_main.c @@ -29,6 +29,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(graphics); WINE_DECLARE_DEBUG_CHANNEL(message);
HMODULE user32_module = 0; +BOOL suppress_timerproc_exception = TRUE;
extern void WDML_NotifyThreadDetach(void);
diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h index a9bbdd16fb0..91612f9c6c5 100644 --- a/dlls/user32/user_private.h +++ b/dlls/user32/user_private.h @@ -44,6 +44,7 @@ struct wm_char_mapping_data };
extern HMODULE user32_module; +extern BOOL suppress_timerproc_exception;
extern BOOL post_dde_message( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, DWORD dest_tid, DWORD type ); diff --git a/dlls/user32/winstation.c b/dlls/user32/winstation.c index 62593ca046f..789e45f8928 100644 --- a/dlls/user32/winstation.c +++ b/dlls/user32/winstation.c @@ -389,12 +389,33 @@ BOOL WINAPI GetUserObjectInformationA( HANDLE handle, INT index, LPVOID info, DW }
+/****************************************************************************** + * SetUserObjectInformationW (USER32.@) + */ +BOOL WINAPI SetUserObjectInformationW( HANDLE handle, INT index, LPVOID info, DWORD len ) +{ + if (index == UOI_TIMERPROC_EXCEPTION_SUPPRESSION) + { + if (handle != GetCurrentProcess() || len != sizeof(BOOL)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + suppress_timerproc_exception = *(const BOOL *)info; + return TRUE; + } + + return NtUserSetObjectInformation( handle, index, info, len ); +} + + /****************************************************************************** * SetUserObjectInformationA (USER32.@) */ BOOL WINAPI SetUserObjectInformationA( HANDLE handle, INT index, LPVOID info, DWORD len ) { - return NtUserSetObjectInformation( handle, index, info, len ); + return SetUserObjectInformationW( handle, index, info, len ); }
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/win32u/tests/win32u.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+)
diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index e118d081ead..f492d56dfa7 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -2023,6 +2023,39 @@ static void test_wndproc_hook(void) UnregisterClassW( L"TestLParamClass", NULL ); }
+static void test_NtUserSetObjectInformation(void) +{ + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( GetProcessWindowStation(), UOI_FLAGS, (void *)NULL, sizeof(USEROBJECTFLAGS) ); + todo_wine + ok( GetLastError() == ERROR_NOACCESS, "NtUserSetObjectInformation error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( GetThreadDesktop(GetCurrentThreadId()), UOI_FLAGS, (void *)NULL, sizeof(USEROBJECTFLAGS) ); + todo_wine + ok( GetLastError() == ERROR_NOACCESS, "NtUserSetObjectInformation error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)TRUE, 0 ); + todo_wine + ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)0xdeadbeef, 0xdeadbeef ); + todo_wine + ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)-1, -1 ); + todo_wine + ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + NtUserSetObjectInformation( NULL, UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)-1, -1 ); + todo_wine + ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() ); +} + START_TEST(win32u) { char **argv; @@ -2066,4 +2099,6 @@ START_TEST(win32u)
test_NtUserEnableMouseInPointer( argv, FALSE ); test_NtUserEnableMouseInPointer( argv, TRUE ); + + test_NtUserSetObjectInformation(); }
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/win32u/tests/win32u.c | 4 ---- dlls/win32u/winstation.c | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index f492d56dfa7..9651dbc357f 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -2037,22 +2037,18 @@ static void test_NtUserSetObjectInformation(void)
SetLastError( 0xdeadbeef ); NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)TRUE, 0 ); - todo_wine ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() );
SetLastError( 0xdeadbeef ); NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)0xdeadbeef, 0xdeadbeef ); - todo_wine ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() );
SetLastError( 0xdeadbeef ); NtUserSetObjectInformation( GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)-1, -1 ); - todo_wine ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() );
SetLastError( 0xdeadbeef ); NtUserSetObjectInformation( NULL, UOI_TIMERPROC_EXCEPTION_SUPPRESSION, (void *)-1, -1 ); - todo_wine ok( GetLastError() == 0xdeadbeef, "NtUserSetObjectInformation error %lu\n", GetLastError() ); }
diff --git a/dlls/win32u/winstation.c b/dlls/win32u/winstation.c index b187b246941..0a84cf03552 100644 --- a/dlls/win32u/winstation.c +++ b/dlls/win32u/winstation.c @@ -395,6 +395,8 @@ BOOL WINAPI NtUserSetObjectInformation( HANDLE handle, INT index, void *info, DW BOOL ret; const USEROBJECTFLAGS *obj_flags = info;
+ if (index == UOI_TIMERPROC_EXCEPTION_SUPPRESSION) return TRUE; + if (index != UOI_FLAGS || !info || len < sizeof(*obj_flags)) { RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/msg.c:
eptr->ContextRecord->Eip++; /* cancel EIP rewinding */
+#elif defined(__x86_64__)
if ((ULONG_PTR)eptr->ExceptionRecord->ExceptionAddress == eptr->ContextRecord->Rip + 1)
eptr->ContextRecord->Rip++; /* cancel RIP rewinding */
+#endif
}
timer_exc_phase = TIMER_EXCEPTION_CONTINUE;
return EXCEPTION_CONTINUE_EXECUTION;
- }
- return EXCEPTION_CONTINUE_SEARCH;
+}
+static void dispatch_message_ansi_handle_exception(const MSG *msg, PVECTORED_EXCEPTION_HANDLER handler) +{
- void *cookie = AddVectoredExceptionHandler(TRUE, handler);
VEH precedes user32 DispatchMessage's SEH, which means this will always catch the exception regardless of the value of `UOI_TIMERPROC_EXCEPTION_SUPPRESSION`.