GetWindowLong() – 改变创造者更新引入的行为打破了我的win32应用程序

最近对Windows 10的创作者更新已经打破了我使用Win32 API GetWindowLong()的应用程序代码。

在Windows 10 Creator Update之前,即使进程B(主线程)在某个系统中被阻塞,一个进程(比如进程A)也能够在另一个进程的窗口句柄(比如进程B)上调用GetWindowWord()/ GetWindowLong调用(例如等待一个互斥量被释放)。 因此,进程A能够成功地使用这些API查询进程B拥有的窗口的保留内存,尽pipe进程B被阻塞了。

但是,在Windows 10上应用创build者更新时,当进程B(主线程)被阻止时,进程A在属于进程B的窗口调用这些API时被阻塞。

我通过创build2个代表进程A和进程B的独立Win32应用程序来模拟这种情况。在应用了Creators Update的Windows 10系统上,进程A在属于进程B的窗口上调用GetWindowLong()/ GetWindowWord()时被吊死进程B(主线程)正在等待一个互斥量。 换句话说,对GetWindowLong()/ GetWindowWord()的调用永远不会返回,从而使进程A挂起。

但是,当我在Windows 10系统上使用独立应用程序在没有创build者更新或早期版本(如Windows 7)的情况下testing相同的场景时,对过程A中GetWindowLong()/ GetWindowWord()API的调用即使进程B正在等待一个互斥量被释放。

为了演示上述问题,这里是进程A和进程B的代码。要查看问题,请运行进程A和进程B.然后,find进程B窗口的窗口句柄(例如使用Spy ++),然后粘贴到进程A的窗口的编辑区域。 然后点击确定。 将显示一个消息框,显示在进程B窗口的额外内存(使用SetWindowLong())中设置的LONG值。 到现在为止还挺好。 现在,进入B的窗口,通过点击“Block”button使其挂起。 这将使进程“B”(主GUI线程)等待一个永远不会被释放的互斥体,因此进程B将挂起。

现在,返回到进程A的窗口,再次单击确定(假定编辑字段仍然具有与先前粘贴的进程B相同的窗口句柄)。

现在,这是行为的差异:

在没有创build者更新的Windows 10和早期的Windows版本(例如Windows 7)(如过程B没有挂起)的情况下,在过程B窗口的额外内存(使用SetWindowLong())中显示LONG值的消息框)被显示。

在创作者更新的Windows 10上,进程A挂起,因为使用进程B的窗口句柄进行的对SetWindowLong()的调用永远不会使进程A挂起。

请build议我如何绕过Windows 10创作者更新行为的这种变化,以便我的应用程序不会挂起。 任何想法/帮助将不胜感激。

以下是过程A的代码

/* Process A */ #include <windows.h> #include <stdio.h> #include <commctrl.h> int count = 0; int count1 = 0; TCHAR str[1000]; LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM); HWND g_hwndEdit, g_hwndButton; #define ID_EDIT (3456) #define ID_OK (3457) TCHAR szWinName[] = TEXT("MyWin"); HINSTANCE g_hInst = NULL; int WINAPI WinMain(HINSTANCE hThisInst,HINSTANCE hPrevInst,LPSTR lpszArgs,int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wcl; g_hInst = hThisInst; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; wcl.lpszClassName = szWinName; wcl.lpfnWndProc = WindowFunc; wcl.style = CS_HREDRAW|CS_VREDRAW; wcl.hIcon = LoadIcon(NULL,IDI_APPLICATION); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL,IDC_ARROW); wcl.lpszMenuName = NULL; wcl.cbClsExtra = 0; wcl.cbWndExtra = 44; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) return 0; hwnd = CreateWindowEx( WS_EX_WINDOWEDGE, szWinName, "Process A", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hThisInst, NULL ); ShowWindow(hwnd,nWinMode); UpdateWindow(hwnd); while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowFunc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { LONG l; HWND hwndOther = hwnd; char s[] = "Paste the window handle (in HEX) of Process B's window on which you wish to call GetWindowLong() in the edit field and click on OK."; HDC hdc; PAINTSTRUCT ps; static int cxClient = 0, cyClient = 0; char btnText[1001]; switch(message){ case WM_CREATE: g_hwndEdit = CreateWindow ("edit", NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER | ES_LEFT, 200, 200, 200, 200, hwnd, (HMENU)ID_EDIT, g_hInst, NULL) ; g_hwndButton = CreateWindow( "Button", "OK", WS_CHILD|WS_VISIBLE, 500, 200, 150, 50, hwnd, (HMENU)ID_OK, g_hInst, NULL ); return 0; case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 100, s, strlen(s)); EndPaint(hwnd, &ps); return 0; case WM_COMMAND: { if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == ID_OK) { GetWindowText(g_hwndEdit, btnText, 1000); sscanf(btnText, "%x", &hwndOther); l = GetWindowLong(hwndOther, 24); sprintf(str, "The LONG value at offset 24 of the window with handle 0x%x is %d.", hwndOther, l); MessageBox(hwnd, str, "", 0); } } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); } 

这里是过程B的代码:

 /* Process B */ #include <windows.h> #include <stdio.h> #include <commctrl.h> int count = 0; int count1 = 0; TCHAR str[1000]; LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM); TCHAR szWinName[] = TEXT("MyWin"); HINSTANCE g_hInst = NULL; HANDLE g_hThread, g_hMutex; HWND g_hwndButton; #define ID_BUTTON (3456) //worker thread fn DWORD WINAPI ThreadFunc(LPVOID p) { g_hMutex = CreateMutex(NULL, TRUE, "HELLO_MUTEX"); // this worker thread now owns the above created mutex and goes into an infinite loop so that // the mutex is never released while (1){} return 0; } // main (GUI) thread int WINAPI WinMain(HINSTANCE hThisInst,HINSTANCE hPrevInst,LPSTR lpszArgs,int nWinMode) { HANDLE hThread; DWORD threadld; // create a worker thread that will create a mutex and then will go into an infinite loop making sure that the mutex is never released // and thus when the main (GUI) thread calls WaitForSingleObject() on this mutex handle, it is going to block forever. hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadld); // make the main (GUI) thread sleep for 5 secs so that by the time it wakes up, the worker thread will have created the mutex and gone into an infinite loop Sleep(5000); HWND hwnd; MSG msg; WNDCLASSEX wcl; g_hInst = hThisInst; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; wcl.lpszClassName = szWinName; wcl.lpfnWndProc = WindowFunc; wcl.style = CS_HREDRAW|CS_VREDRAW; wcl.hIcon = LoadIcon(NULL,IDI_APPLICATION); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL,IDC_ARROW); wcl.lpszMenuName = NULL; wcl.cbClsExtra = 0; wcl.cbWndExtra = 44; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) return 0; hwnd = CreateWindowEx( WS_EX_WINDOWEDGE, szWinName, "Process B", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hThisInst, NULL ); SetWindowLong(hwnd, 24, 135678); ShowWindow(hwnd,nWinMode); UpdateWindow(hwnd); while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowFunc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { char strr[1000]; char s[] = "Click on the \"Block\" button below to make the main (GUI) thread block by waiting on a mutex forever since the mutex will never be released."; HWND hwndOther = hwnd; HDC hdc; PAINTSTRUCT ps; static int cxClient = 0, cyClient = 0; switch(message){ case WM_CREATE: sprintf(strr, "Window created - handle is %x.\n", hwnd); OutputDebugString(strr); g_hwndButton = CreateWindow( "Button", "Block", WS_CHILD|WS_VISIBLE, 10, 120, 50, 50, hwnd, (HMENU)ID_BUTTON, g_hInst, NULL ); return 0; case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 100, s, strlen(s)); EndPaint(hwnd, &ps); return 0; case WM_COMMAND: { if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == ID_BUTTON) { MessageBox(hwnd, "Main (GUI) Thread going in blocking state by waiting for mutex forever now", "", 0); WaitForSingleObject(g_hMutex, INFINITE); } } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); } 

我想我找到了在Process B中使用MsgWaitForMultipleObjects()的解决方案,以便它不断地在队列中查找消息以及等待互斥量。 通过这样做,进程B的窗口句柄上的进程A中的GetWindowLong()调用将正常返回而不会阻塞,因此问题得以解决。

这里是进程B的更新代码,更改是在“阻止”按钮单击处理WM_COMMAND情况下(进程A代码保持不变):

 #include <windows.h> #include <stdio.h> #include <commctrl.h> int count = 0; int count1 = 0; TCHAR str[1000]; LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM); TCHAR szWinName[] = TEXT("MyWin"); HINSTANCE g_hInst = NULL; HANDLE g_hThread, g_hMutex; HWND g_hwndButton; #define ID_BUTTON (3456) //worker thread fn DWORD WINAPI ThreadFunc(LPVOID p) { g_hMutex = CreateMutex(NULL, TRUE, "HELLO_MUTEX"); // this worker thread now owns the above created mutex and goes into an infinite loop so that // the mutex is never released while (1){} return 0; } // main (GUI) thread int WINAPI WinMain(HINSTANCE hThisInst,HINSTANCE hPrevInst,LPSTR lpszArgs,int nWinMode) { HANDLE hThread; DWORD threadld; // create a worker thread that will create a mutex and then will go into an infinite loop making sure that the mutex is never released // and thus when the main (GUI) thread calls WaitForSingleObject() on this mutex handle, it is going to block forever. hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadld); // make the main (GUI) thread sleep for 5 secs so that by the time it wakes up, the worker thread will have created the mutex and gone into an infinite loop Sleep(5000); HWND hwnd; MSG msg; WNDCLASSEX wcl; g_hInst = hThisInst; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; wcl.lpszClassName = szWinName; wcl.lpfnWndProc = WindowFunc; wcl.style = CS_HREDRAW|CS_VREDRAW; wcl.hIcon = LoadIcon(NULL,IDI_APPLICATION); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL,IDC_ARROW); wcl.lpszMenuName = NULL; wcl.cbClsExtra = 0; wcl.cbWndExtra = 44; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) return 0; hwnd = CreateWindowEx( WS_EX_WINDOWEDGE, szWinName, "Process B", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hThisInst, NULL ); SetWindowLong(hwnd, 24, 135678); ShowWindow(hwnd,nWinMode); UpdateWindow(hwnd); while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } BOOL waitWithMessageLoop(HANDLE hMutex, BOOL &bExit) { BOOL bContinue = TRUE; bExit = FALSE; while(bContinue) { DWORD dwReturn = ::MsgWaitForMultipleObjects(1, &hMutex, FALSE, INFINITE, QS_ALLINPUT); if(dwReturn == WAIT_OBJECT_0) { // our mutex got released bContinue = FALSE; } else if(dwReturn == WAIT_OBJECT_0 + 1) { MSG msg; while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { bExit = TRUE; bContinue = FALSE; break; } ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } else { // MsgWaitForMultipleObjects() returned error return FALSE; } } return TRUE; } LRESULT CALLBACK WindowFunc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { char strr[1000]; char s[] = "Click on the \"Block\" button below to make the main (GUI) thread block by waiting on a mutex forever since the mutex will never be released."; HWND hwndOther = hwnd; HDC hdc; PAINTSTRUCT ps; static int cxClient = 0, cyClient = 0; switch(message){ case WM_CREATE: sprintf(strr, "Window created - handle is %x.\n", hwnd); OutputDebugString(strr); g_hwndButton = CreateWindow( "Button", "Block", WS_CHILD|WS_VISIBLE, 10, 120, 50, 50, hwnd, (HMENU)ID_BUTTON, g_hInst, NULL ); return 0; case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 100, s, strlen(s)); EndPaint(hwnd, &ps); return 0; case WM_COMMAND: { if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == ID_BUTTON) { MessageBox(hwnd, "Main (GUI) Thread going in blocking state by waiting for mutex forever now", "", 0); // disable the "Block" button EnableWindow(g_hwndButton, FALSE); //WaitForSingleObject(g_hMutex, INFINITE);// do NOT use this as this cause the GetWindowLong() call made in Process A to hang BOOL bExit = FALSE; waitWithMessageLoop(g_hMutex, bExit); if (bExit) { PostQuitMessage(0); } } } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); } 

谢谢, – ANURAG。