mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-05-15 16:08:05 +02:00
WICFactory: Make thread-safe
This fixes two issues: 1. A global IWICImagingFactory interface pointer was used directly from any apartment without marshaling. This goes against the rules of COM, and for all we know could lead to threading issues (perhaps it's not a problem in practice, but can't tell). 2. The COM apartment where the IWICImagingFactory is created could be finalized by the user. We take a reference on the STA but the thread could terminate. If the thread is on the MTA we don't take a reference at all. In such case the interface pointer would not be valid anymore (per the COM rules), but most importantly the WIC DLL is unloaded (this actually happens). To avoid that we create a IWICImagingFactory transiently each time we need it. We could cache the interface pointer using something like IInitializeSpy or the COM static store [1]. However performance is still great and we'll likely drop use of WIC soon (see MR !607). References: 1. The COM static store, part 1: Introduction https://devblogs.microsoft.com/oldnewthing/20210208-00/?p=104812
This commit is contained in:
parent
efd0cad9ae
commit
df558f592b
3 changed files with 149 additions and 14 deletions
|
|
@ -168,17 +168,35 @@ class WICImagingFactory
|
|||
public:
|
||||
static RefPtr<IWICImagingFactory> Instance()
|
||||
{
|
||||
if (!mFactoryInstance) {
|
||||
CoInitialize(NULL);
|
||||
CoCreateInstance(CLSID_WICImagingFactory,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&mFactoryInstance));
|
||||
}
|
||||
return mFactoryInstance;
|
||||
HRESULT hr;
|
||||
|
||||
/* WIC is based on true COM, so we need to set this thread
|
||||
* on a COM apartment. Usually one calls CoInitialize and
|
||||
* call it a day, however this is used on threads we don't
|
||||
* own. We can use whatever apartment the user has already
|
||||
* initialized, but if there's no apartment we don't want
|
||||
* to force the thread to a specific apartment type. Turns
|
||||
* out implicit MTA is perfect for this; we have to take
|
||||
* a reference on the MTA however, otherwise it can disappear
|
||||
* at any time in the middle of our operations.
|
||||
*
|
||||
* Note: WICImagingFactory has threading model 'both', so
|
||||
* the object will be accessed directly (no marshaling)
|
||||
* regardless of the apartment type.
|
||||
*/
|
||||
cairo_win32_ensure_mta ();
|
||||
|
||||
IWICImagingFactory *wic_factory;
|
||||
hr = CoCreateInstance (CLSID_WICImagingFactory,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS (&wic_factory));
|
||||
if (FAILED (hr)) {
|
||||
assert (0 && "CoCreateInstance (CLSID_WICImagingFactory) failed");
|
||||
}
|
||||
|
||||
return wic_factory;
|
||||
}
|
||||
private:
|
||||
static RefPtr<IWICImagingFactory> mFactoryInstance;
|
||||
};
|
||||
|
||||
cairo_atomic_once_t DWriteFactory::mOnceFactories = CAIRO_ATOMIC_ONCE_INIT;
|
||||
|
|
@ -188,9 +206,6 @@ RefPtr<IDWriteFactory2> DWriteFactory::mFactoryInstance2;
|
|||
RefPtr<IDWriteFactory3> DWriteFactory::mFactoryInstance3;
|
||||
RefPtr<IDWriteFactory4> DWriteFactory::mFactoryInstance4;
|
||||
RefPtr<IDWriteFactory8> DWriteFactory::mFactoryInstance8;
|
||||
|
||||
RefPtr<IWICImagingFactory> WICImagingFactory::mFactoryInstance;
|
||||
|
||||
cairo_atomic_once_t DWriteFactory::mOnceSystemCollection = CAIRO_ATOMIC_ONCE_INIT;
|
||||
RefPtr<IDWriteFontCollection> DWriteFactory::mSystemCollection;
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ _cairo_win32_gdi_compositor_get (void);
|
|||
cairo_status_t
|
||||
_cairo_win32_print_api_error (const char *context, const char *api);
|
||||
|
||||
cairo_status_t
|
||||
_cairo_win32_api_error_fatal (const char *format, ...);
|
||||
|
||||
cairo_bool_t
|
||||
_cairo_surface_is_win32 (const cairo_surface_t *surface);
|
||||
|
||||
|
|
@ -239,6 +242,9 @@ cairo_win32_get_system_text_quality (void);
|
|||
HMODULE
|
||||
_cairo_win32_load_library_from_system32 (const wchar_t *name);
|
||||
|
||||
void
|
||||
cairo_win32_ensure_mta (void);
|
||||
|
||||
typedef DWORD (__stdcall *stdcall_free_func_t) (void *);
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -50,6 +50,27 @@
|
|||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef HRESULT (__stdcall *pCoIncrementMTAUsage_t) (CO_MTA_USAGE_COOKIE*);
|
||||
typedef HRESULT (__stdcall *pCoDecrementMTAUsage_t) (CO_MTA_USAGE_COOKIE);
|
||||
|
||||
static struct {
|
||||
cairo_atomic_once_t once;
|
||||
|
||||
struct {
|
||||
CO_MTA_USAGE_COOKIE cookie;
|
||||
bool cookie_is_set;
|
||||
pCoDecrementMTAUsage_t pCoDecrementMTAUsage;
|
||||
} mta_usage;
|
||||
HANDLE thread;
|
||||
} mta =
|
||||
{
|
||||
CAIRO_ATOMIC_ONCE_INIT,
|
||||
{ 0, false, NULL },
|
||||
NULL,
|
||||
};
|
||||
|
||||
/**
|
||||
* _cairo_win32_print_api_error:
|
||||
* @context: context string to display along with the error
|
||||
|
|
@ -109,6 +130,99 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name)
|
|||
return module_handle;
|
||||
}
|
||||
|
||||
static DWORD __stdcall
|
||||
mta_thread_main (void *user_data)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
hr = CoInitializeEx (NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
assert (SUCCEEDED (hr));
|
||||
|
||||
HANDLE event = (HANDLE) user_data;
|
||||
if (!SignalObjectAndWait (event, GetCurrentProcess (), INFINITE, FALSE)) {
|
||||
assert (0 && "SignalObjectAndWait failed");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cairo_win32_ensure_mta:
|
||||
*
|
||||
* Ensures that the MTA is initialized and keeps running in this
|
||||
* process. Helps for COM usage on threads that we don't own,
|
||||
* since we don't have to call CoInitializeEx.
|
||||
**/
|
||||
void
|
||||
cairo_win32_ensure_mta (void)
|
||||
{
|
||||
if (_cairo_atomic_init_once_enter (&mta.once)) {
|
||||
HMODULE ole32 = _cairo_win32_load_library_from_system32 (L"OLE32.DLL");
|
||||
|
||||
/* Windows 8+ */
|
||||
if (ole32) {
|
||||
pCoIncrementMTAUsage_t pCoIncrementMTAUsage =
|
||||
(pCoIncrementMTAUsage_t) GetProcAddress (ole32, "CoIncrementMTAUsage");
|
||||
pCoDecrementMTAUsage_t pCoDecrementMTAUsage =
|
||||
(pCoDecrementMTAUsage_t) GetProcAddress (ole32, "CoDecrementMTAUsage");
|
||||
|
||||
if (pCoIncrementMTAUsage && pCoDecrementMTAUsage &&
|
||||
SUCCEEDED (pCoIncrementMTAUsage (&mta.mta_usage.cookie)))
|
||||
{
|
||||
mta.mta_usage.cookie_is_set = true;
|
||||
mta.mta_usage.pCoDecrementMTAUsage = pCoDecrementMTAUsage;
|
||||
}
|
||||
}
|
||||
|
||||
/* Downlevel support for Windows 7 */
|
||||
if (!mta.mta_usage.cookie_is_set) {
|
||||
HANDLE event = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||
if (!event) {
|
||||
assert (0 && "CreateEvent failed");
|
||||
}
|
||||
|
||||
/* Since the UCRT _beginthreadex takes a reference on the "calling
|
||||
* HMODULE", which makes Cairo unloadable. Use CreateThread.
|
||||
*/
|
||||
mta.thread = CreateThread (NULL, 0, mta_thread_main, event, 0, NULL);
|
||||
if (!mta.thread) {
|
||||
assert (0 && "_beginthreadex failed");
|
||||
}
|
||||
|
||||
DWORD ret = WaitForSingleObject (event, INFINITE);
|
||||
if (ret != WAIT_OBJECT_0) {
|
||||
assert (0 && "WaitForSingleObject failed");
|
||||
}
|
||||
|
||||
CloseHandle (event);
|
||||
}
|
||||
|
||||
_cairo_atomic_init_once_leave (&mta.once);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cairo_win32_mta_finalize (void)
|
||||
{
|
||||
/* Loader-lock-safe */
|
||||
|
||||
if (_cairo_atomic_init_once_check (&mta.once)) {
|
||||
if (mta.mta_usage.cookie_is_set) {
|
||||
void *free_func = mta.mta_usage.pCoDecrementMTAUsage;
|
||||
cairo_win32_async_stdcall_free (free_func, mta.mta_usage.cookie);
|
||||
}
|
||||
else if (mta.thread) {
|
||||
/* Yeah, TerminateThread is generally unsafe. however, this is synchronized
|
||||
* with entering of kernel-mode (SignalObjectAndWait) and thus is completely
|
||||
* safe. Note also that TerminateThread is asynchronous, so it can be used
|
||||
* from DllMain.
|
||||
*/
|
||||
TerminateThread (mta.thread, 0);
|
||||
CloseHandle (mta.thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data)
|
||||
{
|
||||
|
|
@ -135,8 +249,8 @@ static void
|
|||
cairo_win32_finalize (void)
|
||||
{
|
||||
cairo_win32_dwrite_finalize ();
|
||||
|
||||
cairo_win32_thread_data_finalize ();
|
||||
cairo_win32_mta_finalize ();
|
||||
CAIRO_MUTEX_FINALIZE ();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue