PlaySound(xxx, SND_MEMORY | SND_ASYNC) is almost always a bad idea.

Whenever you submit a crash report to OCA, a bug gets filed in the relevant product database and gets automatically assigned to the developer responsible for the code.  I had a crashing bug in the PlaySound API assigned to me. 

 

In this case, the call was crashing deep inside of the waveOutOpen API and it was crashing because the input WAVEFORMATEX structure was bogus.  The strange thing is that the PlaySound API does some fairly thorough validation of the input WAVEFORMATEX read from the .WAV file and that validation had to have passed to get to the call to waveOutOpen.

I looked a bit deeper and came to the realization that every single one of the crashes (in maybe a dozen different applications) had specified SND_MEMORY | SND_ASYNC in their call to PlaySound.

I’ve talked about that particular combination before in my blog, but I wanted to call it out in a top level post in the hopes that people will stop making this common mistake.

When you call PlaySound with the SND_MEMORY flag, it tells the PlaySound API that instead of reading the audio data from a file, you’re passing in a pointer to memory which holds the wave contents for you.  That’s not controversial, and can be quite handy if (for instance) you want to build a .WAV file in memory instead of calling the wave APIs directly.

When you call PlaySound with the SND_ASYNC flag, that flag tells PlaySound that instead of blocking until the sound has finished playing, the API should return immediately instead of blocking while the sound is played.

 

Neither of these flags is controversial and neither of them is particularly dangerous until you combine the two together.

The problem is that there’s really no way of knowing when the sound has finished playing and thus when the application frees the memory, it’s entirely possible that the PlaySound API is still using it.  That means that if you ever call PlaySound with both of these flags, you stand a very high chance of crashing due to the combination of these behaviors.

 

The unfortunate thing is that this behavior has existed since the SND_MEMORY flag was added back in Windows 3.1.  The only safe way of dealing with this that works on all current Windows operating systems is to call PlaySound(NULL, 0, 0) before freeing the memory – the call to PlaySound(NULL, 0, 0) will block until the currently playing sound has completed playing (or abort the playsound if it hasn’t started yet).