Во время исследования этой проблемы я нашел несколько упоминаний о следующем сценарии онлайн, неизменно в качестве неотвеченных вопросов на форумах программирования. Я надеюсь, что публикация этого здесь будет, по крайней мере, служить для документирования моих результатов.
Во-первых, симптом: во время запуска довольно стандартного кода, который использует waveOutWrite() для вывода звука PCM, иногда я получаю это при работе под отладчиком:
[email protected]()
[email protected]() + 0x28 bytes
[email protected]() + 0x113 bytes
[email protected]() + 0x96 bytes
[email protected]() + 0x32743 bytes
[email protected]() + 0x3a bytes
[email protected]() + 0x40 bytes
[email protected]() + 0x9c bytes
[email protected]() + 0x37 bytes
Хотя очевидным подозреваемым будет куча коррупции где-то еще в коде, я узнал, что это не так. Кроме того, я смог воспроизвести эту проблему, используя следующий код (это часть приложения MFC на основе диалога:)
void CwaveoutDlg::OnBnClickedButton1()
{
WAVEFORMATEX wfx;
wfx.nSamplesPerSec = 44100; /* sample rate */
wfx.wBitsPerSample = 16; /* sample size */
wfx.nChannels = 2;
wfx.cbSize = 0; /* size of _extra_ info */
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
waveOutOpen(&hWaveOut,
WAVE_MAPPER,
&wfx,
(DWORD_PTR)m_hWnd,
0,
CALLBACK_WINDOW );
ZeroMemory(&header, sizeof(header));
header.dwBufferLength = 4608;
header.lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608));
waveOutPrepareHeader(hWaveOut, &header, sizeof(header));
waveOutWrite(hWaveOut, &header, sizeof(header));
}
afx_msg LRESULT CwaveoutDlg::OnWOMDone(WPARAM wParam, LPARAM lParam)
{
HWAVEOUT dev = (HWAVEOUT)wParam;
WAVEHDR *hdr = (WAVEHDR*)lParam;
waveOutUnprepareHeader(dev, hdr, sizeof(WAVEHDR));
GlobalFree(GlobalHandle(hdr->lpData));
ZeroMemory(hdr, sizeof(*hdr));
hdr->dwBufferLength = 4608;
hdr->lpData = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, 4608));
waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, hdr, sizeof(WAVEHDR));
return 0;
}
Прежде чем комментировать это, да - пример кода воспроизводит неинициализированную память. Не пытайтесь делать это, когда ваши динамики полностью перевернуты.
Некоторая отладка обнаружила следующую информацию: waveOutPrepareHeader() заполняет header.reserved с указателем на то, что кажется структурой, содержащей по крайней мере два указателя в качестве первых двух членов. Первый указатель имеет значение NULL. После вызова waveOutWrite() этот указатель устанавливается на указатель, выделенный на глобальной куче. В псевдокоде это выглядит примерно так:
struct Undocumented { void *p1, *p2; } /* This might have more members */
MMRESULT waveOutPrepareHeader( handle, LPWAVEHDR hdr, ...) {
hdr->reserved = (Undocumented*)calloc(sizeof(Undocumented));
/* Do more stuff... */
}
MMRESULT waveOutWrite( handle, LPWAVEHDR hdr, ...) {
/* The following assignment fails rarely, causing the problem: */
hdr->reserved->p1 = malloc( /* chunk of private data */ );
/* Probably more code to initiate playback */
}
Обычно заголовок возвращается в приложение функцией waveCompleteHeader(), внутренней функцией wdmaud.dll. waveCompleteHeader() пытается освободить указатель, выделенный waveOutWrite(), вызывая GlobalHandle()/GlobalUnlock() и друзей. Иногда, GlobalHandle() бомбы, как показано выше.
Теперь причина, из-за которой бомбы GlobalHandle() не вызваны повреждением кучи, как я подозревал вначале, - потому что waveOutWrite() возвращен без установки первого указателя во внутренней структуре на действительный указатель. Я подозреваю, что он освобождает память, указанную этим указателем, перед возвратом, но я еще не разобрал ее.
Это происходит только тогда, когда система воспроизведения волн на буферах низкая, поэтому я использую один заголовок, чтобы воспроизвести это.
В этот момент у меня довольно хороший аргумент в пользу того, что это ошибка в моем приложении. В конце концов, мое приложение даже не работает. Кто-нибудь видел это раньше?
Я вижу это на Windows XP SP2. Звуковая карта от SigmaTel, а версия драйвера - 5.10.0.4995.
Примечания:
Чтобы предотвратить путаницу в будущем, я хотел бы указать, что ответ, предполагающий, что проблема заключается в использовании функции malloc()/free() для управления воспроизводимыми буферами, просто неверна. Вы заметите, что я изменил код выше, чтобы отразить это предложение, чтобы предотвратить больше людей от такой же ошибки - это не имеет значения. Буфер, освобождаемый waveCompleteHeader(), не является тем, который содержит данные PCM, ответственность за освобождение буфера PCM лежит в приложении, и нет необходимости выделять его каким-либо определенным образом.
Кроме того, я уверен, что ни один из вызовов API waveOut, которые я использую, не работает.
В настоящее время я предполагаю, что это либо ошибка в Windows, либо в звуковой драйвер. Особые мнения всегда приветствуются.