From 52ab9d6600c1645a7fc06911d457e7956ebb4c31 Mon Sep 17 00:00:00 2001 From: rossb Date: Mon, 9 May 2011 20:05:34 +0000 Subject: [PATCH] refactored COM initialization for dsound, asio and wasapi to new source file pa_win_coinitialize.c. fixes com uninitialisation bugs in error cases. resolves ticket #128 --- src/hostapi/asio/pa_asio.cpp | 62 ++++++++----- src/hostapi/dsound/pa_win_ds.c | 41 +++----- src/hostapi/wasapi/pa_win_wasapi.c | 50 +++------- src/os/win/pa_win_coinitialize.c | 144 +++++++++++++++++++++++++++++ src/os/win/pa_win_coinitialize.h | 94 +++++++++++++++++++ 5 files changed, 301 insertions(+), 90 deletions(-) create mode 100644 src/os/win/pa_win_coinitialize.c create mode 100644 src/os/win/pa_win_coinitialize.h diff --git a/src/hostapi/asio/pa_asio.cpp b/src/hostapi/asio/pa_asio.cpp index 19f7a21..ff3b180 100644 --- a/src/hostapi/asio/pa_asio.cpp +++ b/src/hostapi/asio/pa_asio.cpp @@ -101,6 +101,8 @@ #include "pa_debugprint.h" #include "pa_ringbuffer.h" +#include "pa_win_coinitialize.h" + /* This version of pa_asio.cpp is currently only targetted at Win32, It would require a few tweaks to work with pre-OS X Macintosh. To make configuration easier, we define WIN32 here to make sure @@ -289,6 +291,8 @@ typedef struct PaUtilAllocationGroup *allocations; + PaWinUtilComInitializationResult comInitializationResult; + AsioDrivers *asioDrivers; void *systemSpecific; @@ -935,12 +939,10 @@ PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device, } /* Unload whatever we loaded in LoadAsioDriver(). - Also balance the call to CoInitialize(0). */ static void UnloadAsioDriver( void ) { ASIOExit(); - CoUninitialize(); } /* @@ -956,23 +958,8 @@ static PaError LoadAsioDriver( PaAsioHostApiRepresentation *asioHostApi, const c ASIOError asioError; int asioIsInitialized = 0; - /* - ASIO uses CoCreateInstance() to load a driver. That requires that - CoInitialize(0) be called for every thread that loads a driver. - It is OK to call CoInitialize(0) multiple times form one thread as long - as it is balanced by a call to CoUninitialize(). See UnloadAsioDriver(). - - The V18 version called CoInitialize() starting on 2/19/02. - That was removed from PA V19 for unknown reasons. - Phil Burk added it back on 6/27/08 so that JSyn would work. - */ - CoInitialize( 0 ); - if( !asioHostApi->asioDrivers->loadDriver( const_cast(driverName) ) ) { - /* If this returns an error then it might be because CoInitialize(0) was removed. - It should be called right before this. - */ result = paUnanticipatedHostError; PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" ); goto error; @@ -1021,7 +1008,7 @@ error: { ASIOExit(); } - CoUninitialize(); + return result; } @@ -1053,6 +1040,24 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } + /* + We initialize COM ourselves here and uninitialize it in Terminate(). + This should be the only COM initialization needed in this module. + + The ASIO SDK may also initialize COM but since we want to reduce dependency + on the ASIO SDK we manage COM initialization ourselves. + + There used to be code that initialized COM in other situations + such as when creating a Stream. This made PA work when calling Pa_CreateStream + from a non-main thread. However we currently consider initialization + of COM in non-main threads to be the caller's responsibility. + */ + result = PaWinUtil_CoInitialize( paASIO, &asioHostApi->comInitializationResult ); + if( result != paNoError ) + { + goto error; + } + asioHostApi->asioDrivers = 0; /* avoid surprises in our error handler below */ asioHostApi->allocations = PaUtil_CreateAllocationGroup(); @@ -1065,7 +1070,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex /* Allocate the AsioDrivers() driver list (class from ASIO SDK) */ try { - asioHostApi->asioDrivers = new AsioDrivers(); /* calls CoInitialize(0) */ + asioHostApi->asioDrivers = new AsioDrivers(); /* invokes CoInitialize(0) in AsioDriverList::AsioDriverList */ } catch (std::bad_alloc) { @@ -1347,8 +1352,11 @@ error: delete asioHostApi->asioDrivers; asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaWinUtil_CoUninitialize( paASIO, &asioHostApi->comInitializationResult ); + PaUtil_FreeMemory( asioHostApi ); } + return result; } @@ -1368,9 +1376,11 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); } - delete asioHostApi->asioDrivers; /* calls CoUninitialize() */ + delete asioHostApi->asioDrivers; asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaWinUtil_CoUninitialize( paASIO, &asioHostApi->comInitializationResult ); + PaUtil_FreeMemory( asioHostApi ); } @@ -3836,7 +3846,12 @@ PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) int asioIsInitialized = 0; PaAsioHostApiRepresentation *asioHostApi; PaAsioDeviceInfo *asioDeviceInfo; + PaWinUtilComInitializationResult comInitializationResult; + /* initialize COM again here, we might be in another thread */ + result = PaWinUtil_CoInitialize( paASIO, &comInitializationResult ); + if( result != paNoError ) + return result; result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); if( result != paNoError ) @@ -3863,9 +3878,6 @@ PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; - /* See notes about CoInitialize(0) in LoadAsioDriver(). */ - CoInitialize(0); - if( !asioHostApi->asioDrivers->loadDriver( const_cast(asioDeviceInfo->commonDeviceInfo.name) ) ) { result = paUnanticipatedHostError; @@ -3914,7 +3926,6 @@ PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErr goto error; } - CoUninitialize(); PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); return result; @@ -3924,7 +3935,8 @@ error: { ASIOExit(); } - CoUninitialize(); + + PaWinUtil_CoUninitialize( paASIO, &comInitializationResult ); return result; } diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index e99aca5..df9cad5 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -74,6 +74,7 @@ #include "pa_win_ds_dynlink.h" #include "pa_win_waveformat.h" #include "pa_win_wdmks_utils.h" +#include "pa_win_coinitialize.h" #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ #pragma comment( lib, "dsound.lib" ) @@ -186,7 +187,7 @@ typedef struct /* implementation specific data goes here */ - char comWasInitialized; + PaWinUtilComInitializationResult comInitializationResult; } PaWinDsHostApiRepresentation; @@ -1011,32 +1012,15 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde int i, deviceCount; PaWinDsHostApiRepresentation *winDsHostApi; DSDeviceNamesAndGUIDs deviceNamesAndGUIDs; - PaWinDsDeviceInfo *deviceInfoArray; - char comWasInitialized = 0; - - /* - If COM is already initialized CoInitialize will either return - FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different - threading mode. In either case we shouldn't consider it an error - but we need to be careful to not call CoUninitialize() if - RPC_E_CHANGED_MODE was returned. - */ - - HRESULT hr = CoInitialize(NULL); - if( FAILED(hr) && hr != RPC_E_CHANGED_MODE ) - return paUnanticipatedHostError; - if( hr != RPC_E_CHANGED_MODE ) - comWasInitialized = 1; + PaWinDs_InitializeDSoundEntryPoints(); /* initialise guid vectors so they can be safely deleted on error */ deviceNamesAndGUIDs.winDsHostApi = NULL; deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL; deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL; - PaWinDs_InitializeDSoundEntryPoints(); - winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); if( !winDsHostApi ) { @@ -1044,7 +1028,11 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde goto error; } - winDsHostApi->comWasInitialized = comWasInitialized; + result = PaWinUtil_CoInitialize( paDirectSound, &winDsHostApi->comInitializationResult ); + if( result != paNoError ) + { + goto error; + } winDsHostApi->allocations = PaUtil_CreateAllocationGroup(); if( !winDsHostApi->allocations ) @@ -1183,15 +1171,16 @@ error: PaUtil_FreeAllAllocations( winDsHostApi->allocations ); PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); } - + + PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); + PaUtil_FreeMemory( winDsHostApi ); } TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - if( comWasInitialized ) - CoUninitialize(); + PaWinDs_TerminateDSoundEntryPoints(); return result; } @@ -1201,7 +1190,6 @@ error: static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) { PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; - char comWasInitialized = winDsHostApi->comWasInitialized; /* IMPLEMENT ME: @@ -1214,12 +1202,11 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); } + PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); + PaUtil_FreeMemory( winDsHostApi ); PaWinDs_TerminateDSoundEntryPoints(); - - if( comWasInitialized ) - CoUninitialize(); } diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 4d05207..d382432 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -75,6 +75,8 @@ #include "pa_debugprint.h" #include "pa_ringbuffer.h" +#include "pa_win_coinitialize.h" + #ifndef NTDDI_VERSION #undef WINVER @@ -370,6 +372,8 @@ typedef struct /* implementation specific data goes here */ + PaWinUtilComInitializationResult comInitializationResult; + //in case we later need the synch IMMDeviceEnumerator *enumerator; @@ -512,8 +516,6 @@ void *PaWasapi_ReallocateMemory(void *ptr, size_t size); void PaWasapi_FreeMemory(void *ptr); // Local statics -static volatile BOOL g_WasapiCOMInit = FALSE; -static volatile DWORD g_WasapiInitThread = 0; // ------------------------------------------------------------------------------------------ #define LogHostError(HRES) __LogHostError(HRES, __FUNCTION__, __FILE__, __LINE__) @@ -1057,26 +1059,6 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd return paNoError; } - /* - If COM is already initialized CoInitialize will either return - FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different - threading mode. In either case we shouldn't consider it an error - but we need to be careful to not call CoUninitialize() if - RPC_E_CHANGED_MODE was returned. - */ - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) - { - PRINT(("WASAPI: failed CoInitialize")); - return paUnanticipatedHostError; - } - if (hr != RPC_E_CHANGED_MODE) - g_WasapiCOMInit = TRUE; - - // Memorize calling thread id and report warning on Uninitialize if calling thread is different - // as CoInitialize must match CoUninitialize in the same thread. - g_WasapiInitThread = GetCurrentThreadId(); - paWasapi = (PaWasapiHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaWasapiHostApiRepresentation) ); if (paWasapi == NULL) { @@ -1084,6 +1066,12 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd goto error; } + result = PaWinUtil_CoInitialize( paWASAPI, &paWasapi->comInitializationResult ); + if( result != paNoError ) + { + goto error; + } + paWasapi->allocations = PaUtil_CreateAllocationGroup(); if (paWasapi->allocations == NULL) { @@ -1454,26 +1442,12 @@ static void Terminate( PaUtilHostApiRepresentation *hostApi ) PaUtil_DestroyAllocationGroup(paWasapi->allocations); } + PaWinUtil_CoUninitialize( paWASAPI, &paWasapi->comInitializationResult ); + PaUtil_FreeMemory(paWasapi); // Close AVRT CloseAVRT(); - - // Uninit COM (checking calling thread we won't unitialize user's COM if one is calling - // Pa_Unitialize by mistake from not initializing thread) - if (g_WasapiCOMInit) - { - DWORD calling_thread_id = GetCurrentThreadId(); - if (g_WasapiInitThread != calling_thread_id) - { - PRINT(("WASAPI: failed CoUninitializes calling thread[%d] does not match initializing thread[%d]\n", - calling_thread_id, g_WasapiInitThread)); - } - else - { - CoUninitialize(); - } - } } // ------------------------------------------------------------------------------------------ diff --git a/src/os/win/pa_win_coinitialize.c b/src/os/win/pa_win_coinitialize.c new file mode 100644 index 0000000..2fea85f --- /dev/null +++ b/src/os/win/pa_win_coinitialize.c @@ -0,0 +1,144 @@ +/* + * Microsoft COM initialization routines + * Copyright (c) 1999-2011 Ross Bencina, Dmitry Kostjuchenko + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2011 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup win_src + + @brief Microsoft COM initialization routines. +*/ + +#include +#include + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_debugprint.h" + +#include "pa_win_coinitialize.h" + + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) && !defined(_WIN32_WCE) /* MSC version 6 and above */ +#pragma comment( lib, "ole32.lib" ) +#endif + + +/* use some special bit patterns here to try to guard against uninitialized memory errors */ +#define PAWINUTIL_COM_INITIALIZED (0xb38f) +#define PAWINUTIL_COM_NOT_INITIALIZED (0xf1cd) + + +PaError PaWinUtil_CoInitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult ) +{ + HRESULT hr; + + comInitializationResult->state = PAWINUTIL_COM_NOT_INITIALIZED; + + /* + If COM is already initialized CoInitialize will either return + FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different + threading mode. In either case we shouldn't consider it an error + but we need to be careful to not call CoUninitialize() if + RPC_E_CHANGED_MODE was returned. + */ + + hr = CoInitialize(0); /* use legacy-safe equivalent to CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) */ + if( FAILED(hr) && hr != RPC_E_CHANGED_MODE ) + { + PA_DEBUG(("CoInitializeEx failed. hr=%d\n", hr)); + + if( hr == E_OUTOFMEMORY ) + return paInsufficientMemory; + + { + char *lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + hr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( hostApiType, hr, lpMsgBuf ); + LocalFree( lpMsgBuf ); + } + + return paUnanticipatedHostError; + } + + if( hr != RPC_E_CHANGED_MODE ) + { + comInitializationResult->state = PAWINUTIL_COM_INITIALIZED; + + /* + Memorize calling thread id and report warning on Uninitialize if + calling thread is different as CoInitialize must match CoUninitialize + in the same thread. + */ + comInitializationResult->initializingThreadId = GetCurrentThreadId(); + } + + return paNoError; +} + + +void PaWinUtil_CoUninitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult ) +{ + if( comInitializationResult->state != PAWINUTIL_COM_NOT_INITIALIZED + && comInitializationResult->state != PAWINUTIL_COM_INITIALIZED ){ + + PA_DEBUG(("ERROR: PaWinUtil_CoUninitialize called without calling PaWinUtil_CoInitialize\n")); + } + + if( comInitializationResult->state == PAWINUTIL_COM_INITIALIZED ) + { + DWORD currentThreadId = GetCurrentThreadId(); + if( comInitializationResult->initializingThreadId != currentThreadId ) + { + PA_DEBUG(("ERROR: failed PaWinUtil_CoUninitialize calling thread[%d] does not match initializing thread[%d]\n", + currentThreadId, comInitializationResult->initializingThreadId)); + } + else + { + CoUninitialize(); + + comInitializationResult->state = PAWINUTIL_COM_NOT_INITIALIZED; + } + } +} \ No newline at end of file diff --git a/src/os/win/pa_win_coinitialize.h b/src/os/win/pa_win_coinitialize.h new file mode 100644 index 0000000..a76337c --- /dev/null +++ b/src/os/win/pa_win_coinitialize.h @@ -0,0 +1,94 @@ +/* + * Microsoft COM initialization routines + * Copyright (c) 1999-2011 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup win_src + + @brief Microsoft COM initialization routines. +*/ + +#ifndef PA_WIN_COINITIALIZE_H +#define PA_WIN_COINITIALIZE_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** + @brief Data type used to hold the result of an attempt to initialize COM + using PaWinUtil_CoInitialize. Must be retained between a call to + PaWinUtil_CoInitialize and a matching call to PaWinUtil_CoUninitialize. +*/ +typedef struct PaWinUtilComInitializationResult{ + int state; + int initializingThreadId; +} PaWinUtilComInitializationResult; + + +/** + @brief Initialize Microsoft COM subsystem on the current thread. + + @param hostApiType the host API type id of the caller. Used for error reporting. + + @param comInitializationResult An output parameter. The value pointed to by + this parameter stores information required by PaWinUtil_CoUninitialize + to correctly uninitialize COM. The value should be retained and later + passed to PaWinUtil_CoUninitialize. + + If PaWinUtil_CoInitialize returns paNoError, the caller must later call + PaWinUtil_CoUninitialize once. +*/ +PaError PaWinUtil_CoInitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult ); + + +/** + @brief Uninitialize the Microsoft COM subsystem on the current thread using + the result of a previous call to PaWinUtil_CoInitialize. Must be called on the same + thread as PaWinUtil_CoInitialize. + + @param hostApiType the host API type id of the caller. Used for error reporting. + + @param comInitializationResult An input parameter. A pointer to a value previously + initialized by a call to PaWinUtil_CoInitialize. +*/ +void PaWinUtil_CoUninitialize( PaHostApiTypeId hostApiType, PaWinUtilComInitializationResult *comInitializationResult ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_WIN_COINITIALIZE_H */ -- 2.43.0