-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#39] Integrate with existing (main) Windows GUI thread #40
base: develop
Are you sure you want to change the base?
Conversation
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.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## develop #40 +/- ##
========================================
Coverage 85.95% 85.95%
========================================
Files 40 40
Lines 840 840
========================================
Hits 722 722
Misses 118 118 ☔ View full report in Codecov by Sentry. |
Hi @RippeR37, I think one change that would be worthwhile would be to switch to a message-only window. The main reason for that being that there might be multiple top-level windows in an application. In that situation, top-level windows could be created and destroyed, so attaching to a specific application window wouldn't be safe. By creating a message-only window itself, libbase can offer its functionality to an application, regardless of how the application manages its windows. It also means that the application doesn't need to supply a LIBBASE_TASK_MSG_ID, since the message-only window can use whatever message value it likes. That is, I think the #pragma once
#ifdef LIBBASE_IS_WINDOWS
#include <optional>
#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 {
class WinThreadAttachment {
private:
struct Private {
private:
Private() = default;
friend WinThreadAttachment;
};
public:
static std::optional<WinThreadAttachment> TryCreate() {
HWND hWnd = TryCreateMessageOnlyWindow();
if (!hWnd) {
return std::nullopt;
}
return std::make_optional<WinThreadAttachment>(hWnd, Private());
}
WinThreadAttachment(HWND hWnd, Private)
: hWnd_(hWnd),
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;
}
~WinThreadAttachment() {
DCHECK_NE(WinThreadAttachment::current_instance_, nullptr);
WinThreadAttachment::current_instance_ = nullptr;
message_pump_->Stop({});
DestroyWindow(hWnd_);
}
std::shared_ptr<SingleThreadTaskRunner> TaskRunner() const {
return task_runner_;
}
private:
static HWND TryCreateMessageOnlyWindow() {
static int static_in_this_module = 0;
HMODULE hModule = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(&static_in_this_module),
&hModule);
WNDCLASSA window_class = {};
window_class.lpfnWndProc = WindowProc;
window_class.lpszClassName = LIBBASE_CLASS_NAME;
window_class.hInstance = hModule;
window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
RegisterClassA(&window_class);
return CreateWindowA(LIBBASE_CLASS_NAME, "", 0, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND_MESSAGE, nullptr, hModule, nullptr);
}
static LRESULT CALLBACK WindowProc(HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_LIBBASE_EXECUTE_TASK: {
DCHECK_NE(WinThreadAttachment::current_instance_, nullptr);
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);
}
inline static thread_local WinThreadAttachment* current_instance_ = nullptr;
inline static const char LIBBASE_CLASS_NAME[] = "libbaseMessageOnlyWindow";
static const UINT WM_LIBBASE_EXECUTE_TASK = WM_USER + 0;
HWND hWnd_;
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 The window creation can technically fail, which is the reason for the Aside from that, I've done some testing I think it should all work well. |
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:
Apart from that, new (Windows-only) example called
win32
has been added to showcase new functionality.