#include #include #include #include #include #include #include namespace { constexpr UINT ProbeMessage = WM_APP + 0x41; std::atomic probeWndProcThread{ 0 }; std::atomic keyWndProcThread{ 0 }; std::atomic keyWndProcCount{ 0 }; HANDLE probeObserved = nullptr; WNDPROC previousWndProc = nullptr; LRESULT CALLBACK ProbeWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == ProbeMessage) { probeWndProcThread.store(GetCurrentThreadId(), std::memory_order_release); if (probeObserved) SetEvent(probeObserved); } else if (message == WM_KEYDOWN || message == WM_KEYUP) { keyWndProcThread.store(GetCurrentThreadId(), std::memory_order_release); keyWndProcCount.fetch_add(1, std::memory_order_relaxed); } return previousWndProc ? CallWindowProcA(previousWndProc, hwnd, message, wParam, lParam) : DefWindowProcA(hwnd, message, wParam, lParam); } bool postFixedKey(HWND hwnd) { constexpr LPARAM KeyDownLParam = 1 | (0x1E << 16); constexpr LPARAM KeyUpLParam = KeyDownLParam | (1LL << 30) | (1LL << 31); return PostMessageA(hwnd, WM_KEYDOWN, 'A', KeyDownLParam) != FALSE && PostMessageA(hwnd, WM_KEYUP, 'A', KeyUpLParam) != FALSE; } } int main(int argc, char** argv) { const std::string outputPath = argc > 1 ? argv[1] : "easyx-thread-probe.json"; const DWORD processId = GetCurrentProcessId(); const DWORD mainThreadBeforeInit = GetCurrentThreadId(); HWND hwnd = initgraph(320, 180, 1); const DWORD mainThreadAfterInit = GetCurrentThreadId(); DWORD windowProcessId = 0; const DWORD windowOwnerThread = GetWindowThreadProcessId(hwnd, &windowProcessId); const bool windowUnicode = IsWindowUnicode(hwnd) != FALSE; ShowWindow(hwnd, SW_HIDE); probeObserved = CreateEventW(nullptr, TRUE, FALSE, nullptr); SetLastError(ERROR_SUCCESS); previousWndProc = reinterpret_cast( SetWindowLongPtrA(hwnd, GWLP_WNDPROC, reinterpret_cast(&ProbeWndProc))); const DWORD subclassError = previousWndProc ? ERROR_SUCCESS : GetLastError(); bool probePosted = false; bool probeDelivered = false; bool keyPosted = false; DWORD peekCallerThread = 0; unsigned int easyxKeyCount = 0; if (previousWndProc && probeObserved) { probePosted = PostMessageA(hwnd, ProbeMessage, 0, 0) != FALSE; probeDelivered = probePosted && WaitForSingleObject(probeObserved, 5000) == WAIT_OBJECT_0; keyPosted = postFixedKey(hwnd); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (std::chrono::steady_clock::now() < deadline && easyxKeyCount < 2) { ExMessage message{}; peekCallerThread = GetCurrentThreadId(); if (peekmessage(&message, EX_KEY, true)) { if ((message.message == WM_KEYDOWN || message.message == WM_KEYUP) && message.vkcode == 'A') { ++easyxKeyCount; } } else { Sleep(1); } } } const DWORD observedProbeThread = probeWndProcThread.load(std::memory_order_acquire); const DWORD observedKeyThread = keyWndProcThread.load(std::memory_order_acquire); const unsigned int observedKeyWndProcCount = keyWndProcCount.load(std::memory_order_relaxed); const bool separateWindowThread = windowOwnerThread != 0 && windowOwnerThread != mainThreadAfterInit; const bool wndProcMatchesOwner = observedProbeThread != 0 && observedProbeThread == windowOwnerThread; const bool keyCrossedBothBackends = keyPosted && observedKeyWndProcCount >= 2 && easyxKeyCount >= 2; const bool completed = hwnd != nullptr && previousWndProc != nullptr && probeDelivered && wndProcMatchesOwner && keyCrossedBothBackends; { std::ofstream output(outputPath, std::ios::trunc); output << "{\n" << " \"schemaVersion\": 1,\n" << " \"processId\": " << processId << ",\n" << " \"windowProcessId\": " << windowProcessId << ",\n" << " \"mainThreadBeforeInit\": " << mainThreadBeforeInit << ",\n" << " \"mainThreadAfterInit\": " << mainThreadAfterInit << ",\n" << " \"windowOwnerThread\": " << windowOwnerThread << ",\n" << " \"probeWndProcThread\": " << observedProbeThread << ",\n" << " \"keyWndProcThread\": " << observedKeyThread << ",\n" << " \"peekCallerThread\": " << peekCallerThread << ",\n" << " \"windowUnicode\": " << (windowUnicode ? "true" : "false") << ",\n" << " \"subclassError\": " << subclassError << ",\n" << " \"probePosted\": " << (probePosted ? "true" : "false") << ",\n" << " \"probeDelivered\": " << (probeDelivered ? "true" : "false") << ",\n" << " \"keyPosted\": " << (keyPosted ? "true" : "false") << ",\n" << " \"keyWndProcCount\": " << observedKeyWndProcCount << ",\n" << " \"easyxKeyCount\": " << easyxKeyCount << ",\n" << " \"separateWindowThread\": " << (separateWindowThread ? "true" : "false") << ",\n" << " \"wndProcMatchesOwner\": " << (wndProcMatchesOwner ? "true" : "false") << ",\n" << " \"keyCrossedBothBackends\": " << (keyCrossedBothBackends ? "true" : "false") << ",\n" << " \"completed\": " << (completed ? "true" : "false") << "\n" << "}\n"; } if (previousWndProc) SetWindowLongPtrA(hwnd, GWLP_WNDPROC, reinterpret_cast(previousWndProc)); if (probeObserved) CloseHandle(probeObserved); closegraph(); return completed ? 0 : 2; }