From 1681ec976745c9725486eddcc8317b66b0c21bc2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 27 Oct 2020 17:04:40 +0100 Subject: [PATCH] Add support for lambdas to Win32Thread --- src/wine-host/utils.cpp | 23 ++++++++++++++ src/wine-host/utils.h | 67 ++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/wine-host/utils.cpp b/src/wine-host/utils.cpp index 3ef90392..7d8d86c8 100644 --- a/src/wine-host/utils.cpp +++ b/src/wine-host/utils.cpp @@ -18,6 +18,29 @@ PluginContext::PluginContext() : context(), events_timer(context) {} +uint32_t WINAPI win32_thread_trampoline(std::function* entry_point) { + (*entry_point)(); + delete entry_point; + + return 0; +} + +Win32Thread::~Win32Thread() { + // Imitate std::jthread's joining behaviour, the thread handle will be + // cleaned up by the `std::unique_ptr` + if (handle) { + WaitForSingleObject(handle.get(), INFINITE); + } +} + +Win32Thread::Win32Thread(Win32Thread&& o) : handle(std::move(o.handle)) {} + +Win32Thread& Win32Thread::operator=(Win32Thread&& o) { + handle = std::move(o.handle); + + return *this; +} + void PluginContext::run() { context.run(); } diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 20d52bf3..b5c04e6e 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -114,23 +114,29 @@ class PluginContext { }; /** - * A simple RAII wrapper around the Win32 thread API. + * A proxy function that calls `Win32Thread::entry_point` since `CreateThread()` + * is not usable with lambdas directly. Calling the passed function will invoke + * the lambda with the arguments passed during `Win32Thread`'s constructor. This + * function deallocates the function after it's finished executing. * - * These threads are implemented using `CreateThread` rather than `std::thread` - * because in some cases `std::thread` in winelib causes very hard to debug data - * races within plugins such as Serum. This might be caused by calling - * conventions being handled differently. + * We can't store the function pointer in the `Win32Thread` object because + * moving a `Win32Thread` object would then cause issues. * - * This somewhat mimicks `std::thread`, with the following differences: + * @param win32_thread_trampoline A `std::function*` pointer to a + * function pointer, great. + */ +uint32_t WINAPI win32_thread_trampoline(std::function* entry_point); + +/** + * A simple RAII wrapper around the Win32 thread API that imitates + * `std::jthread`. * - * - The threads will immediatly be killed silently when a `Win32Thread` object - * goes out of scope. This is the desired behavior in our case since the host - * will have already saved chunk data before closing the plugin and this - * ensures that the plugin shuts down quickly. - * - This does not accept lambdas because we're calling a C function that - * expects a function pointer of type `LPTHREAD_START_ROUTINE`. GCC supports - * converting stateless lambdas to this format, but clang (as used for IDE - * tooling) does not. + * `std::thread` directly uses pthreads. This means that, like with + * `CreateThread()`, some thread local information does not get initialized + * which can lead to memory errors. + * + * TODO: Once these changes are complete, check if we can drop `PluginContext` + * again and execute all 'safe' opcodes on the calling thread. * * @note This should be used instead of `std::thread` or `std::jthread` whenever * the thread directly calls third party library code, i.e. `LoadLibrary()`, @@ -145,19 +151,38 @@ class Win32Thread { Win32Thread(); /** - * Constructor that immediately starts running the thread + * Constructor that immediately starts running the thread. This works + * equivalently to `std::jthread`. * * @param entry_point The thread entry point that should be run. * @param parameter The parameter passed to the entry point function. - - * @tparam F A function type that should be convertible to a - * `LPTHREAD_START_ROUTINE` function pointer. */ - template - Win32Thread(F entry_point, void* parameter) - : handle(CreateThread(nullptr, 0, entry_point, parameter, 0, nullptr), + template + Win32Thread(Function&& f, Args&&... args) + : handle(CreateThread( + nullptr, + 0, + reinterpret_cast( + win32_thread_trampoline), + new std::function( + [f = std::move(f), ... args = std::move(args)]() { + std::invoke(f, args...); + }), + 0, + nullptr), CloseHandle) {} + /** + * Join the thread on destruction, just like `std::jthread` does. + */ + ~Win32Thread(); + + Win32Thread(const Win32Thread&) = delete; + Win32Thread& operator=(const Win32Thread&) = delete; + + Win32Thread(Win32Thread&&); + Win32Thread& operator=(Win32Thread&&); + private: /** * The handle for the thread that is running, will be a null pointer if this