For React Native.
-- v2: win32u: Put the message in the queue when callback pointer is NULL and callback data is 1. user32/tests: Add more SendMessageCallbackA/W() tests with NULL callback.
From: Zhiyi Zhang zzhang@codeweavers.com
The tests are mainly to show that SendMessageCallbackA/W() with NULL callback pointer and callback data being 1 causes the message to be posted to the window, even when the window is created in the same thread. --- dlls/user32/tests/msg.c | 68 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 80d900baa1c..24be627a78d 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -16499,16 +16499,82 @@ static void test_EndDialog(void) UnregisterClassA(cls.lpszClassName, cls.hInstance); }
+static const struct message WmUserSeq[] = +{ + { WM_USER, sent }, + { 0 } +}; + static void test_nullCallback(void) { + DWORD status; HWND hwnd; + BOOL ret;
hwnd = CreateWindowExA(0, "TestWindowClass", "Test overlapped", WS_OVERLAPPEDWINDOW, 100, 100, 200, 200, 0, 0, 0, NULL); ok (hwnd != 0, "Failed to create overlapped window\n");
- SendMessageCallbackA(hwnd,WM_NULL,0,0,NULL,0); + /* NULL callback and data being 0 with SendMessageCallbackA() */ + flush_sequence(); + ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 0); + ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); + ok_sequence(WmUserSeq, "WM_USER with NULL callback", FALSE); + + /* NULL callback and data being 0 with SendMessageCallbackW() */ + flush_sequence(); + ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 0); + ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); + ok_sequence(WmUserSeq, "WM_USER with NULL callback", FALSE); + + /* NULL callback and data being 1 with SendMessageCallbackA(). The result suggests that the + * message is not directly sent to the window */ + flush_sequence(); + ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 1); + ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); + ok_sequence(WmEmptySeq, "WM_USER with NULL callback", TRUE); flush_events(); + ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", TRUE); + + /* NULL callback and data being 1 with SendMessageCallbackW(). The result suggests that the + * message is not directly sent to the window */ + flush_sequence(); + ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 1); + ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); + ok_sequence(WmEmptySeq, "WM_USER with NULL callback", TRUE); + flush_events(); + ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", TRUE); + + /* NULL callback and data being 2 with SendMessageCallbackA() */ + flush_sequence(); + ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 2); + ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); + ok_sequence(WmUserSeq, "WM_USER with NULL callback", FALSE); + + /* NULL callback and data being 2 with SendMessageCallbackW() */ + flush_sequence(); + ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 2); + ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); + ok_sequence(WmUserSeq, "WM_USER with NULL callback", FALSE); + + /* Check the queue status after SendMessageCallbackA() with NULL callback and data being 1 */ + flush_events(); + ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 1); + ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); + status = GetQueueStatus(QS_ALLINPUT); + todo_wine + ok(HIWORD(status) & QS_SENDMESSAGE && LOWORD(status) & QS_SENDMESSAGE, + "Got unexpected status %#lx.\n", status); + + /* Check the queue status after SendMessageCallbackW() with NULL callback and data being 1 */ + flush_events(); + ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 1); + ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); + status = GetQueueStatus(QS_ALLINPUT); + todo_wine + ok(HIWORD(status) & QS_SENDMESSAGE && LOWORD(status) & QS_SENDMESSAGE, + "Got unexpected status %#lx.\n", status); + DestroyWindow(hwnd); }
From: Zhiyi Zhang zzhang@codeweavers.com
This is an undocumented behavior needed by React Native apps, based on Nikolay's findings. --- dlls/user32/tests/msg.c | 10 ++++------ dlls/win32u/message.c | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 24be627a78d..696cf41505e 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -16532,18 +16532,18 @@ static void test_nullCallback(void) flush_sequence(); ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 1); ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); - ok_sequence(WmEmptySeq, "WM_USER with NULL callback", TRUE); + ok_sequence(WmEmptySeq, "WM_USER with NULL callback", FALSE); flush_events(); - ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", TRUE); + ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", FALSE);
/* NULL callback and data being 1 with SendMessageCallbackW(). The result suggests that the * message is not directly sent to the window */ flush_sequence(); ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 1); ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); - ok_sequence(WmEmptySeq, "WM_USER with NULL callback", TRUE); + ok_sequence(WmEmptySeq, "WM_USER with NULL callback", FALSE); flush_events(); - ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", TRUE); + ok_sequence(WmUserSeq, "WM_USER with NULL callback after flushing events", FALSE);
/* NULL callback and data being 2 with SendMessageCallbackA() */ flush_sequence(); @@ -16562,7 +16562,6 @@ static void test_nullCallback(void) ret = SendMessageCallbackA(hwnd, WM_USER, 0, 0, NULL, 1); ok(ret, "SendMessageCallbackA failed, error %ld.", GetLastError()); status = GetQueueStatus(QS_ALLINPUT); - todo_wine ok(HIWORD(status) & QS_SENDMESSAGE && LOWORD(status) & QS_SENDMESSAGE, "Got unexpected status %#lx.\n", status);
@@ -16571,7 +16570,6 @@ static void test_nullCallback(void) ret = SendMessageCallbackW(hwnd, WM_USER, 0, 0, NULL, 1); ok(ret, "SendMessageCallbackW failed, error %ld.", GetLastError()); status = GetQueueStatus(QS_ALLINPUT); - todo_wine ok(HIWORD(status) & QS_SENDMESSAGE && LOWORD(status) & QS_SENDMESSAGE, "Got unexpected status %#lx.\n", status);
diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 2a824dbdab3..e64e8213f93 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -4189,7 +4189,9 @@ static BOOL process_message( struct send_message_info *info, DWORD_PTR *res_ptr, thread_info->msg_source = msg_source_unavailable; spy_enter_message( SPY_SENDMESSAGE, info->hwnd, info->msg, info->wparam, info->lparam );
- if (info->dest_tid != GetCurrentThreadId()) + if (info->dest_tid != GetCurrentThreadId() || + /* undocumented behavior when callback pointer is NULL and data is 1. See test_nullCallback(). */ + (info->type == MSG_CALLBACK && !info->callback && info->data == 1)) { if (dest_pid != GetCurrentProcessId() && (info->type == MSG_ASCII || info->type == MSG_UNICODE)) info->type = MSG_OTHER_PROCESS;
On Mon Aug 4 09:07:57 2025 +0000, Dmitry Timoshkov wrote:
The queue status would confirm that the message is supposed to be posted and not sent.
Thanks. I've added tests for the queue status.
On Mon Aug 4 09:14:13 2025 +0000, Zhiyi Zhang wrote:
Thanks. I've added tests for the queue status.
There's no need to add another copy of SendMessageCallback() tests just for checking the queue status.