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

This commit is contained in:
rossb 2011-05-09 20:05:34 +00:00
commit 52ab9d6600
5 changed files with 301 additions and 90 deletions

View file

@ -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<char*>(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<char*>(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;
}

View file

@ -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();
}

View file

@ -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();
}
}
}
// ------------------------------------------------------------------------------------------

View file

@ -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 <windows.h>
#include <objbase.h>
#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;
}
}
}

View file

@ -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 */