Skip to content

Commit

Permalink
[#39] Integrate with existing (main) Windows GUI thread
Browse files Browse the repository at this point in the history
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<LIBBASE_TASK_MSG_ID> 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.
  • Loading branch information
RippeR37 committed Feb 11, 2024
1 parent 5f1b049 commit 0bdf995
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 1 deletion.
6 changes: 5 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
add_subdirectory(simple)
add_subdirectory(simple)

if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_subdirectory(win32)
endif ()
9 changes: 9 additions & 0 deletions examples/win32/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
87 changes: 87 additions & 0 deletions examples/win32/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#ifndef UNICODE
#define UNICODE
#endif

#include <thread>

#include "base/threading/thread_pool.h"
#include "base/threading/win/win_thread_attachment.h"

#include <windows.h>

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<LIBBASE_TASK_MSG_ID> 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);
}
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
$<$<PLATFORM_ID:Windows>:base/message_loop/win/message_pump_win_impl.h>
base/sequence_checker.cc
base/sequence_checker.h
base/sequence_id.cc
Expand Down Expand Up @@ -85,6 +86,7 @@ target_sources(libbase
base/threading/thread.h
base/threading/thread_pool.cc
base/threading/thread_pool.h
$<$<PLATFORM_ID:Windows>:base/threading/win/win_thread_attachment.h>
base/time/time.cc
base/time/time.h
base/time/time_delta.cc
Expand Down
32 changes: 32 additions & 0 deletions src/base/message_loop/win/message_pump_win_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#ifdef LIBBASE_IS_WINDOWS

#include <windows.h>

#include "base/message_loop/message_pump_impl.h"

namespace base {

template <UINT WM_LIBBASE_EXECUTE_TASK>
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
90 changes: 90 additions & 0 deletions src/base/threading/win/win_thread_attachment.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once

#ifdef LIBBASE_IS_WINDOWS

#include <windows.h>

#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 <UINT WM_LIBBASE_EXECUTE_TASK>
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<MessagePumpWinImpl<WM_LIBBASE_EXECUTE_TASK>>(
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<SingleThreadTaskRunner> TaskRunner() const {
return task_runner_;
}

private:
inline static thread_local WinThreadAttachment* current_instance_ = nullptr;

HWND hWnd_;
WNDPROC wndProc_;

SequenceId sequence_id_;
std::shared_ptr<MessagePump> message_pump_;
std::shared_ptr<SingleThreadTaskRunnerImpl> task_runner_;
detail::ScopedSequenceIdSetter scoped_sequence_id_;
SequencedTaskRunnerHandle scoped_task_runner_handle_;
};

} // namespace base

#endif // LIBBASE_IS_WINDOWS

0 comments on commit 0bdf995

Please sign in to comment.