From 1f5230dde0a9c5663511397476aa378860bbd52b Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:10:13 +0100 Subject: [PATCH] GDI: Use thread data for font HDC Use the newly-introduced thread data structure to store the per-thread font HDC. As an added bonus, we can now avoid leaking HDCs on module unlaods. --- src/win32/cairo-win32-font.c | 32 ++++++++++++++--------------- src/win32/cairo-win32-private.h | 14 +++++++++++-- src/win32/cairo-win32-system.c | 6 ++++++ src/win32/cairo-win32-thread-data.c | 10 +++++++++ 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/win32/cairo-win32-font.c b/src/win32/cairo-win32-font.c index 21c19a141..d57d86b4e 100644 --- a/src/win32/cairo-win32-font.c +++ b/src/win32/cairo-win32-font.c @@ -143,19 +143,13 @@ _cairo_win32_scaled_font_init_glyph_path (cairo_win32_scaled_font_t *scaled_font static HDC _get_global_font_dc (void) { - static cairo_atomic_once_t once = CAIRO_ATOMIC_ONCE_INIT; - static DWORD hdc_tls_index; - HDC hdc; + cairo_win32_thread_data_t *data = cairo_win32_thread_data_get (); - if (_cairo_atomic_init_once_enter (&once)) { - hdc_tls_index = TlsAlloc (); - assert (hdc_tls_index != TLS_OUT_OF_INDEXES); - _cairo_atomic_init_once_leave (&once); - } + if (!data->hdc) { + HDC hdc_screen = GetDC (NULL); + HDC hdc; - hdc = TlsGetValue (hdc_tls_index); - if (!hdc) { - hdc = CreateCompatibleDC (NULL); + hdc = CreateCompatibleDC (hdc_screen); if (!hdc) { fprintf (stderr, "%s:%s\n", __FUNCTION__, "CreateCompatibleDC"); return NULL; @@ -167,13 +161,19 @@ _get_global_font_dc (void) return NULL; } - if (!TlsSetValue (hdc_tls_index, hdc)) { - DeleteDC (hdc); - return NULL; - } + data->hdc = hdc; + /* From MSDN docs for CreateCompatibleDC: + * + * If [the reference] hdc is NULL, the thread that calls CreateCompatibleDC + * owns the HDC that is created. When this thread is destroyed, the HDC is + * no longer valid. + */ + data->free_hdc = (hdc_screen != NULL); + + ReleaseDC (NULL, hdc_screen); } - return hdc; + return data->hdc; } static cairo_status_t diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index 4b4f7f912..410a95aa8 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -36,14 +36,16 @@ #ifndef CAIRO_WIN32_PRIVATE_H #define CAIRO_WIN32_PRIVATE_H -#include "cairo-win32.h" - #include "cairoint.h" #include "cairo-device-private.h" #include "cairo-surface-clipper-private.h" #include "cairo-surface-private.h" +#include "cairo-win32.h" + +#include + #define WIN32_FONT_LOGICAL_SCALE 32 CAIRO_BEGIN_DECLS @@ -236,7 +238,15 @@ cairo_win32_get_system_text_quality (void); HMODULE _cairo_win32_load_library_from_system32 (const wchar_t *name); +typedef DWORD (__stdcall *stdcall_free_func_t) (void *); + +void +cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data); + typedef struct { + HDC hdc; + cairo_bool_t free_hdc; + cairo_bool_t added_to_list; } cairo_win32_thread_data_t; diff --git a/src/win32/cairo-win32-system.c b/src/win32/cairo-win32-system.c index 7646c4925..ce7ece96f 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -109,6 +109,12 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name) return module_handle; } +void +cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data) +{ + QueueUserWorkItem (func, data, WT_EXECUTEDEFAULT); +} + static void cairo_win32_initialize (void) { diff --git a/src/win32/cairo-win32-thread-data.c b/src/win32/cairo-win32-thread-data.c index 593202d87..169f6b9ce 100644 --- a/src/win32/cairo-win32-thread-data.c +++ b/src/win32/cairo-win32-thread-data.c @@ -97,6 +97,16 @@ thread_data_free (cairo_win32_thread_data_t *data) { /* Loader-lock-safe */ + if (data->free_hdc) { + /* Delete the HDC explicitly only if it was created via a non-NULL + * reference HDC. Otherwise the system deletes it automatically on + * thread-exit and our asynchronous delete would be racy. For more + * informations, refer to the MSDN docs for CreateCompatibleDC. + */ + void *free_func = DeleteDC; + cairo_win32_async_stdcall_free (free_func, data->hdc); + } + #ifdef USE_EXPLICIT_TLS thread_data_allocation_free (data); #endif