Search code examples
c++winapicom

Avoiding invalidated pointers while using WinAPI (Windows Core Audio)


We are using an instantiation of IMMNotificationClient to notify us of changes that happen with a specific audio device (the Foo sound blaster), but when a Foo blaster that has not yet been installed on the computer yet is plugged in, the program seg-faults (throws an Access Violation) inside of the IMMNotificationClient::OnDeviceStateChanged function

Here's what's causing this to happen as far as I can tell:

  1. plug in the new Foo blaster
  2. Windows fires off a device state change notification to the IMMNotificationClient
  3. Before the IMMNotificationClient::OnDeviceStateChanged function can finish execution, Windows invalidates the COM object.

This invalidation happens at seemingly random points in the IMMNotificationClient::OnDeviceStateChanged function.

Here's some sample code:

#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <string>

class FooSoundBlasterNotifier: public IMMNotificationClient {
    //private member variables
    const char * FOO_BLASTER_NAME = "Foo Blaster";


public:
    HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pDeviceId, DWORD newState)
    {
        IMMDeviceEnumerator *pDeviceEnumerator;
        IMMDevice *pDevice;
        IPropertyStore *pStore;
        HRESULT hr = CoInitialize(NULL);
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pDeviceEnumerator);

        if(SUCCEEDED(hr))
        {
            hr = pDeviceEnumerator->GetDevice(pDeviceId, &pDevice);

            if(SUCCEEDED(hr))
            {
                hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);

                if (SUCCEEDED(hr))
                {
                    PROPVARIANT variant;
                    PropVariantInit(&variant);

                    hr = pStore->GetValue(PKEY_Device_FriendlyName, &variant);

                    if (SUCCEEDED(hr))
                    {
                        //Code usually crashes about right here
                        std::wstring friendlyNameW(variant.pwszVal);
                        std::string friendlyName(friendlyNameW.begin(), friendlyNameW.end());
                        if(friendlyName.find(FOO_BLASTER_NAME) != std::string::npos)
                        {
                            //Log the information about state change
                        }
                        //release
                    }
                    //all
                }
                //COM
            }
            //Objects
        }

        return S_OK;
    }

    //Declare other needed functions
};

How can I avoid using an invalidated Windows COM object? Baring that, how do I successfully recover from an access violation without having to close down the whole program?

EDIT

Here's a call trace of where the code is failing:

common_strnlen_simd<1,1,unsigned short>(const unsigned short * const string, const unsigned __int64 maximum_count) Line 152
    at minkernel\crts\ucrt\src\appcrt\string\strnlen.cpp(152)
common_strnlen<1,unsigned short>(const unsigned short * const string, const unsigned __int64 maximum_count) Line 185
    at minkernel\crts\ucrt\src\appcrt\string\strnlen.cpp(185)
wcslen(const wchar_t * string) Line 219
    at minkernel\crts\ucrt\src\appcrt\string\strnlen.cpp(219)
[External Code]
FooSoundBlaster::OnDeviceStateChanged(const wchar_t * pwstrDeviceId, unsigned long dwNewState)
[External Code]

Solution

  • The real problem isn't an invalidation of the IMMDevice object, but rather variant.pwszVal being null. A simple check like this:

    if(variant.pwszVal /* != NULL, there you go Paul! :) */)
    {
        friendlyNameW = variant.pwszVal;
    }
    

    should solve the problem in the above code.