Created
January 13, 2026 13:33
-
-
Save ihewro/0bddd0f9b2f56bb4aadcf5a746f616f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <windows.h> | |
| #include <cstdio> | |
| #include <string> | |
| #include <chrono> | |
| // ----------------- Common logging utilities ----------------- | |
| std::string NowTimeString() { | |
| SYSTEMTIME st; | |
| GetLocalTime(&st); | |
| char buf[64]; | |
| std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", | |
| st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); | |
| return buf; | |
| } | |
| void Log(const char* threadName, int seq, const char* msg, | |
| BOOL ret = -1, DWORD err = (DWORD)-1) { | |
| std::string time = NowTimeString(); | |
| DWORD tid = GetCurrentThreadId(); | |
| if (ret == (BOOL)-1) { | |
| std::printf("[%s] [TID=%lu] [%s] #%d %s\n", | |
| time.c_str(), tid, threadName, seq, msg); | |
| } else { | |
| std::printf("[%s] [TID=%lu] [%s] #%d %s ret=%d err=%lu\n", | |
| time.c_str(), tid, threadName, seq, msg, (int)ret, err); | |
| } | |
| std::fflush(stdout); | |
| } | |
| // ----------------- Global: message window handle ----------------- | |
| HWND g_hMsgWnd = nullptr; | |
| // ----------------- Simple message window ----------------- | |
| LRESULT CALLBACK MsgWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { | |
| switch (msg) { | |
| case WM_DESTROY: | |
| PostQuitMessage(0); | |
| return 0; | |
| default: | |
| return DefWindowProc(hWnd, msg, wParam, lParam); | |
| } | |
| } | |
| HWND CreateMessageWindow(HINSTANCE hInst) { | |
| const wchar_t CLASS_NAME[] = L"ClipboardTestMsgWindowClass"; | |
| WNDCLASSEXW wcex = {}; | |
| wcex.cbSize = sizeof(WNDCLASSEXW); | |
| wcex.style = CS_HREDRAW | CS_VREDRAW; | |
| wcex.lpfnWndProc = MsgWndProc; | |
| wcex.hInstance = hInst; | |
| wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); | |
| wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); | |
| wcex.lpszClassName = CLASS_NAME; | |
| if (!RegisterClassExW(&wcex)) { | |
| std::printf("RegisterClassExW failed, GetLastError=%lu\n", GetLastError()); | |
| return nullptr; | |
| } | |
| HWND hWnd = CreateWindowExW( | |
| 0, | |
| CLASS_NAME, | |
| L"ClipboardTestMsgWindow", | |
| WS_OVERLAPPEDWINDOW, | |
| CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, | |
| nullptr, | |
| nullptr, | |
| hInst, | |
| nullptr | |
| ); | |
| if (!hWnd) { | |
| std::printf("CreateWindowExW failed, GetLastError=%lu\n", GetLastError()); | |
| return nullptr; | |
| } | |
| // The window does not need to be shown; you can comment out ShowWindow | |
| // ShowWindow(hWnd, SW_HIDE); | |
| return hWnd; | |
| } | |
| // ----------------- Thread A: hold the clipboard for 20 seconds ----------------- | |
| DWORD WINAPI ThreadA(LPVOID) { | |
| const char* name = "ThreadA"; | |
| int seq = 0; | |
| Log(name, seq++, "Start"); | |
| HWND hWnd = g_hMsgWnd; | |
| if (!hWnd) { | |
| Log(name, seq++, "g_hMsgWnd is NULL, exiting"); | |
| return 0; | |
| } | |
| BOOL ok = ::OpenClipboard(hWnd); | |
| DWORD err = ok ? 0 : ::GetLastError(); | |
| Log(name, seq++, "OpenClipboard (first, with hWnd)", ok, err); | |
| if (!ok) { | |
| Log(name, seq++, "OpenClipboard failed, exiting"); | |
| return 0; | |
| } | |
| Log(name, seq++, "Holding clipboard for 20 seconds..."); | |
| ::Sleep(20000); | |
| ::CloseClipboard(); | |
| Log(name, seq++, "CloseClipboard"); | |
| Log(name, seq++, "End"); | |
| return 0; | |
| } | |
| // ----------------- Thread B: repeatedly try to open and close the clipboard ----------------- | |
| DWORD WINAPI ThreadB(LPVOID) { | |
| const char* name = "ThreadB"; | |
| int seq = 0; | |
| Log(name, seq++, "Start"); | |
| HWND hWnd = g_hMsgWnd; | |
| if (!hWnd) { | |
| Log(name, seq++, "g_hMsgWnd is NULL, exiting"); | |
| return 0; | |
| } | |
| auto start = std::chrono::steady_clock::now(); | |
| auto endTime = start + std::chrono::seconds(30); | |
| int attempt = 0; | |
| while (std::chrono::steady_clock::now() < endTime) { | |
| BOOL ok = ::OpenClipboard(hWnd); | |
| DWORD err = ok ? 0 : ::GetLastError(); | |
| char buf[128]; | |
| std::snprintf(buf, sizeof(buf), "OpenClipboard attempt=%d (with hWnd)", attempt); | |
| Log(name, seq++, buf, ok, err); | |
| if (ok) { | |
| ::CloseClipboard(); | |
| Log(name, seq++, "CloseClipboard after successful OpenClipboard"); | |
| } | |
| ++attempt; | |
| ::Sleep(200); // Try once every 200ms | |
| } | |
| Log(name, seq++, "End"); | |
| return 0; | |
| } | |
| // ----------------- main ----------------- | |
| int main() { | |
| std::printf("=== Clipboard multi-thread OpenClipboard(hWnd) test ===\n"); | |
| std::fflush(stdout); | |
| HINSTANCE hInst = GetModuleHandle(nullptr); | |
| // 1. Create the message window | |
| g_hMsgWnd = CreateMessageWindow(hInst); | |
| if (!g_hMsgWnd) { | |
| std::printf("Failed to create message window, exit.\n"); | |
| return 1; | |
| } | |
| std::printf("Message window created: HWND=0x%p\n", g_hMsgWnd); | |
| // 2. Create thread A and B | |
| HANDLE hThreadA = ::CreateThread(nullptr, 0, ThreadA, nullptr, 0, nullptr); | |
| HANDLE hThreadB = ::CreateThread(nullptr, 0, ThreadB, nullptr, 0, nullptr); | |
| if (!hThreadA || !hThreadB) { | |
| std::printf("Failed to create threads. GetLastError=%lu\n", GetLastError()); | |
| return 1; | |
| } | |
| HANDLE handles[2] = { hThreadA, hThreadB }; | |
| ::WaitForMultipleObjects(2, handles, TRUE, INFINITE); | |
| ::CloseHandle(hThreadA); | |
| ::CloseHandle(hThreadB); | |
| std::printf("=== Test finished ===\n"); | |
| return 0; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ThreadA:OpenClipboard and Holding clipboard for 20 seconds...
ThreadB:Also OpenClipboard success!