Niedawna aktualizacja twórców do systemu Windows 10 zepsuła mój kod aplikacji, która używa Win32 API GetWindowLong().GetWindowLong() - Zmiana w zachowaniu wprowadzona przy pomocy aktualizacji twórców łamie moją aplikację win32
Przed aktualizacją dla twórców systemu Windows 10 jeden proces (na przykład proces A) mógł wywoływać funkcje API GetWindowWord()/GetWindowLong() na uchwycie okna innego procesu (np. Proces B), nawet jeśli proces B (główny wątek) był zablokowane w niektórych wywołaniach systemu (np. oczekiwanie na zwolnienie muteksa). Tak więc proces A był w stanie przesłać kwerendę do zarezerwowanej pamięci okna, którego właścicielem jest proces B, z powodzeniem wykorzystując te interfejsy API pomimo zablokowania procesu B.
Jednak w przypadku Aktualizacji twórców zastosowanych w systemie Windows 10 proces A zostaje zablokowany, gdy wywołuje te interfejsy API w oknie należącym do procesu B, gdy proces B (główny wątek) jest zablokowany.
Symulowałem ten scenariusz, tworząc dwie samodzielne aplikacje Win32 reprezentujące proces A i proces B. W systemie Windows 10 z zainstalowaną aktualizacją twórców proces A powieszony, gdy nazywał się GetWindowLong()/GetWindowWord() w oknie należącym do do przetwarzania B, podczas gdy proces B (główny wątek) czekał na muteksie. Innymi słowy, wywołania GetWindowLong()/GetWindowWord() nigdy nie powróciły, powodując zawieszenie procesu A.
Jednak podczas testowania tego samego scenariusza z moimi autonomicznymi aplikacjami w systemie Windows 10 bez Aktualizacji twórców lub wcześniejszej wersji, takiej jak Windows 7, wywołania funkcji GetWindowLong()/GetWindowWord() w procesie A powrócić pomyślnie, nawet gdy proces B oczekuje na zwolnienie muteksu.
Aby zademonstrować powyższy problem, oto kod zarówno dla Procesu A, jak i dla Procesu B. Aby zobaczyć problem, uruchom proces A i proces B. Następnie znajdź uchwyt okna okna Procesu B (np. Za pomocą Spy ++), a następnie wklej go do pola edycji okna procesu A. Następnie kliknij OK. Wyświetlane jest okno komunikatu wyświetlające wartość LONG ustawioną w dodatkowej pamięci okna procesu B (za pomocą SetWindowLong()). Jak dotąd, tak dobrze. Teraz przejdź do okna procesu B i spraw, aby zawiesił się, klikając przycisk "Zablokuj". Spowoduje to, że proces "B" (główny wątek GUI) będzie czekał na muteksie, który nigdy nie zostanie zwolniony, a zatem proces B zawiesi się.
TERAZ, wróć do procesu A i ponownie kliknij OK (zakładając, że pole edycyjne ma nadal ten sam uchwyt okna procesu B, który wkleiłeś wcześniej).
TERAZ, tu jest różnica w zachowaniu:
W Windows 10 Bez twórców Update i we wcześniejszych wersjach systemu Windows, takich jak Windows 7, jak poprzednio (czyli gdy proces B nie zawiesza się), wyświetlony zostanie komunikat wyświetlania wartość LONG ustawiona w dodatkowej pamięci okna procesu B (za pomocą SetWindowLong()) jest pokazana.
W systemie Windows 10 Z aktualizacją Kreator, proces A zawiesza się, ponieważ wywołanie SetWindowLong() wykonane przy użyciu uchwytu okna procesu B nigdy nie wraca, powodując zawieszenie procesu A.
Pls sugerują, jak obejść zmianę zachowania w aktualizacji Windows 10 Creators Update, aby moja aplikacja nie zawiesiła się. Wszelkie pomysły/pomoc będą mile widziane.
Oto kod dla procesu 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);
}
A oto kod dla Process 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);
}
Jeśli to naprawdę jest coś, co zmieniło się w CU, jest mało prawdopodobne, aby cokolwiek można z tym zrobić. –
naprawdę nici gui nie może czekać i "wisiał". musi na stałe uruchomić pętlę wiadomości. więc rozwiązanie bardzo prosto - nie czekaj, bez przetwarzania komunikatów, w gui thread – RbMm
Dzięki RbMm za wskazanie mnie we właściwym kierunku. Twoja sugestia implikowała użycie MsgWaitForMultipleObjects(), która nie spowoduje zawieszenia wątku GUI w Procesie B, a zatem GetWindowLong() wykonane w Procesie A na uchwycie okna Procesu B powróci normalnie bez zwisania. –