From 0bdf99551aaf1e72c83aa7d1cecd6968d3adb7a2 Mon Sep 17 00:00:00 2001 From: RippeR37 Date: Sun, 11 Feb 2024 02:18:11 +0100 Subject: [PATCH] [#39] Integrate with existing (main) Windows GUI thread This commit adds a new class template called `base::WinThreadAttachment` which can be used to integrate libbase's cross-thread post-tasking with Windows native message queue system. Example usage: ```cpp HWND hwnd = CreateWindowEx(/* ... */); // This must be done after creating a window and before main loop and // outlive the main loop. // `LIBBASE_TASK_MSG_ID` here is some custom integer provided by user // of this library that will not be used as any other message ID in // the app (e.g. `WM_APP+0`). base::WinThreadAttachment mainThread{hwnd}; // As long as the `mainThread` lives, current thread will have // associated task runner that will post task to Window's message // queue and all such tasks will be executed between other messages // on that queue. // Main loop example MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } ``` Apart from there, new (Windows-only) example called `win32` has been added to showcase new functionality. --- examples/CMakeLists.txt | 6 +- examples/win32/CMakeLists.txt | 9 ++ examples/win32/main.cc | 87 ++++++++++++++++++ src/CMakeLists.txt | 2 + .../message_loop/win/message_pump_win_impl.h | 32 +++++++ .../threading/win/win_thread_attachment.h | 90 +++++++++++++++++++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 examples/win32/CMakeLists.txt create mode 100644 examples/win32/main.cc create mode 100644 src/base/message_loop/win/message_pump_win_impl.h create mode 100644 src/base/threading/win/win_thread_attachment.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6b23a955..722a073d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,5 @@ -add_subdirectory(simple) \ No newline at end of file +add_subdirectory(simple) + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_subdirectory(win32) +endif () diff --git a/examples/win32/CMakeLists.txt b/examples/win32/CMakeLists.txt new file mode 100644 index 00000000..2f81712b --- /dev/null +++ b/examples/win32/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(win32 WIN32 "") + +target_compile_options(win32 PRIVATE ${LIBBASE_COMPILE_FLAGS}) +target_link_libraries(win32 PRIVATE libbase) + +target_sources(win32 + PRIVATE + main.cc +) diff --git a/examples/win32/main.cc b/examples/win32/main.cc new file mode 100644 index 00000000..c1eaa224 --- /dev/null +++ b/examples/win32/main.cc @@ -0,0 +1,87 @@ +#ifndef UNICODE +#define UNICODE +#endif + +#include + +#include "base/threading/thread_pool.h" +#include "base/threading/win/win_thread_attachment.h" + +#include + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void SetWindowTitle(HWND hWnd, const wchar_t* title) { + SetWindowText(hWnd, title); +} + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) { + // Register the window class. + const wchar_t CLASS_NAME[] = L"Window Class"; + WNDCLASS wc = {}; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + RegisterClass(&wc); + + HWND hwnd = CreateWindowEx( + 0, CLASS_NAME, L"Default title", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); + if (hwnd == NULL) { + return 0; + } + + ShowWindow(hwnd, nCmdShow); + + // Setup libbase attachment to main Window thread's message loop + constexpr unsigned int LIBBASE_TASK_MSG_ID = WM_APP + 0; + base::WinThreadAttachment mainThread{hwnd}; + + // Some example testing + mainThread.TaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&SetWindowTitle, hwnd, + L"Changed window title from post-task")); + mainThread.TaskRunner()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&SetWindowTitle, hwnd, L"Delayed task executed as well!"), + base::Seconds(3)); + + base::ThreadPool threadPool{4}; + threadPool.Start(); + threadPool.GetTaskRunner()->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce([]() -> const wchar_t* { + std::this_thread::sleep_for(std::chrono::seconds(5)); + return L"Greetings from thread pool!"; + }), + base::BindOnce(&SetWindowTitle, hwnd)); + + // Run the message loop. + MSG msg = {}; + while (GetMessage(&msg, NULL, 0, 0) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} + +LRESULT CALLBACK WindowProc(HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + switch (uMsg) { + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); + EndPaint(hwnd, &ps); + } + return 0; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8007f125..e350fb1c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(libbase base/message_loop/message_pump.h base/message_loop/message_pump_impl.cc base/message_loop/message_pump_impl.h + $<$:base/message_loop/win/message_pump_win_impl.h> base/sequence_checker.cc base/sequence_checker.h base/sequence_id.cc @@ -85,6 +86,7 @@ target_sources(libbase base/threading/thread.h base/threading/thread_pool.cc base/threading/thread_pool.h + $<$:base/threading/win/win_thread_attachment.h> base/time/time.cc base/time/time.h base/time/time_delta.cc diff --git a/src/base/message_loop/win/message_pump_win_impl.h b/src/base/message_loop/win/message_pump_win_impl.h new file mode 100644 index 00000000..2dc3a2af --- /dev/null +++ b/src/base/message_loop/win/message_pump_win_impl.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef LIBBASE_IS_WINDOWS + +#include + +#include "base/message_loop/message_pump_impl.h" + +namespace base { + +template +class MessagePumpWinImpl : public MessagePumpImpl { + public: + MessagePumpWinImpl(size_t executors_count, HWND hwnd) + : MessagePumpImpl(executors_count), hwnd_(hwnd) {} + + // MessagePump + bool QueuePendingTask(PendingTask pending_task) override { + if (MessagePumpImpl::QueuePendingTask(std::move(pending_task))) { + PostMessage(hwnd_, WM_LIBBASE_EXECUTE_TASK, 0, 0); + return true; + } + return false; + } + + private: + HWND hwnd_; +}; + +} // namespace base + +#endif // LIBBASE_IS_WINDOWS diff --git a/src/base/threading/win/win_thread_attachment.h b/src/base/threading/win/win_thread_attachment.h new file mode 100644 index 00000000..6c747a16 --- /dev/null +++ b/src/base/threading/win/win_thread_attachment.h @@ -0,0 +1,90 @@ +#pragma once + +#ifdef LIBBASE_IS_WINDOWS + +#include + +#include "base/message_loop/win/message_pump_win_impl.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/threading/delayed_task_manager_shared_instance.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/task_runner_impl.h" + +namespace base { + +template +class WinThreadAttachment { + public: + static LRESULT CALLBACK WindowProc(HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + DCHECK_NE(WinThreadAttachment::current_instance_, nullptr); + + switch (uMsg) { + case WM_LIBBASE_EXECUTE_TASK: { + const MessagePump::ExecutorId executor_id = 0; + if (auto pending_task = + WinThreadAttachment::current_instance_->message_pump_ + ->GetNextPendingTask(executor_id)) { + std::move(pending_task.task).Run(); + } + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + + default: + return WinThreadAttachment::current_instance_->wndProc_(hWnd, uMsg, + wParam, lParam); + } + } + + WinThreadAttachment(HWND hWnd) + : hWnd_(hWnd), + wndProc_((WNDPROC)GetWindowLongPtr(hWnd_, GWLP_WNDPROC)), + sequence_id_(detail::SequenceIdGenerator::GetNextSequenceId()), + message_pump_( + std::make_shared>( + 1, + hWnd_)), + task_runner_(SingleThreadTaskRunnerImpl::Create( + message_pump_, + sequence_id_, + 0, + DelayedTaskManagerSharedInstance::GetOrCreateSharedInstance())), + scoped_sequence_id_(sequence_id_), + scoped_task_runner_handle_(task_runner_) { + DCHECK_EQ(WinThreadAttachment::current_instance_, nullptr); + WinThreadAttachment::current_instance_ = this; + + SetWindowLongPtr(hWnd_, GWLP_WNDPROC, + (LONG_PTR)WinThreadAttachment::WindowProc); + } + + ~WinThreadAttachment() { + DCHECK_NE(WinThreadAttachment::current_instance_, nullptr); + WinThreadAttachment::current_instance_ = nullptr; + + message_pump_->Stop({}); + SetWindowLongPtr(hWnd_, GWLP_WNDPROC, (LONG_PTR)wndProc_); + } + + std::shared_ptr TaskRunner() const { + return task_runner_; + } + + private: + inline static thread_local WinThreadAttachment* current_instance_ = nullptr; + + HWND hWnd_; + WNDPROC wndProc_; + + SequenceId sequence_id_; + std::shared_ptr message_pump_; + std::shared_ptr task_runner_; + detail::ScopedSequenceIdSetter scoped_sequence_id_; + SequencedTaskRunnerHandle scoped_task_runner_handle_; +}; + +} // namespace base + +#endif // LIBBASE_IS_WINDOWS