При изучении этой проблемы я нашел несколько упоминаний о следующем сценарии онлайн, неизменно в качестве неотвеченных вопросов на форумах программирования. Надеюсь, что публикация этого здесь будет, по крайней мере, служить для документирования моих результатов.Почему waveOutWrite() вызывает исключение в куче отладки?
Во-первых, симптом: Пока работает довольно стандартный код, который использует 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.
Примечания:
Чтобы избежать путаницы в будущем я хотел бы отметить, что ответ свидетельствует о том, что проблема заключается в использовании таНос()/Free() для управления буферами воспроизводимого просто неправильно. Вы заметите, что я изменил код выше, чтобы отразить это предложение, чтобы предотвратить больше людей от такой же ошибки - это не имеет значения. Буфер, освобождаемый waveCompleteHeader(), не является тем, который содержит данные PCM, ответственность за освобождение буфера PCM лежит на приложении, и нет необходимости выделять его каким-либо определенным образом.
Кроме того, я уверен, что ни один из вызовов API waveOut, которые я использую, не работает.
В настоящее время я предполагаю, что это либо ошибка в Windows, либо в звуковом драйвере. Особые мнения всегда приветствуются.
Я смутно помню, что видел что-то подобное в приложении Windows CE. Deleaker сообщил, что какая-то волновая функция имела утечку памяти. В конце концов, все было в порядке, и когда я закрыл, мне просто пришлось выпустить ресурс. У меня здесь нет кода. – OregonGhost 2008-10-12 17:11:52