この問題を調査しているときに、オンラインで次のシナリオについて複数の言及を見つけましたが、プログラミング フォーラムでは常に未回答の質問として扱われていました。これをここに投稿することが、少なくとも私の調査結果を文書化するのに役立つことを願っています.
まず、症状: waveOutWrite() を使用して PCM オーディオを出力する非常に標準的なコードを実行しているときに、デバッガーで実行すると、次のようになることがあります。
ntdll.dll!_DbgBreakPoint@0()
ntdll.dll!_RtlpBreakPointHeap@4() + 0x28 bytes
ntdll.dll!_RtlpValidateHeapEntry@12() + 0x113 bytes
ntdll.dll!_RtlDebugGetUserInfoHeap@20() + 0x96 bytes
ntdll.dll!_RtlGetUserInfoHeap@20() + 0x32743 bytes
kernel32.dll!_GlobalHandle@4() + 0x3a bytes
wdmaud.drv!_waveCompleteHeader@4() + 0x40 bytes
wdmaud.drv!_waveThread@4() + 0x9c bytes
kernel32.dll!_BaseThreadStart@8() + 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 に、最初の 2 つのメンバーとして少なくとも 2 つのポインターを含む構造体のように見えるものへのポインターを設定します。最初のポインターは 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 */
}
通常、ヘッダーは、wdmaud.dll の内部関数である waveCompleteHeader() によってアプリケーションに返されます。waveCompleteHeader() は、GlobalHandle()/GlobalUnlock() などを呼び出して、waveOutWrite() によって割り当てられたポインターの割り当てを解除しようとします。上記のように、GlobalHandle() が爆発することがあります。
さて、最初に疑ったように、GlobalHandle() が爆発する理由は、ヒープの破損によるものではありません。内部構造の最初のポインターを有効なポインターに設定せずに waveOutWrite() が返されたためです。戻る前に、そのポインタが指すメモリを解放すると思われますが、まだ逆アセンブルしていません。
これは、Wave 再生システムのバッファーが不足している場合にのみ発生するようです。そのため、これを再現するために単一のヘッダーを使用しています。
この時点で、これが私のアプリケーションのバグであるということに対してかなり良いケースがあります。結局のところ、私のアプリケーションは実行さえしていません。誰もこれを見たことがありますか?
これは Windows XP SP2 で見られます。オーディオ カードは SigmaTel 製で、ドライバーのバージョンは 5.10.0.4995 です。
ノート:
将来の混乱を防ぐために、再生中のバッファを管理するために malloc()/free() を使用することに問題があることを示唆する答えは単に間違っていることを指摘したいと思います。より多くの人が同じ間違いを犯すのを防ぐために、提案を反映するために上記のコードを変更したことに注意してください。違いはありません。waveCompleteHeader() によって解放されるバッファーは、PCM データを含むものではなく、PCM バッファーを解放する責任はアプリケーションにあり、特定の方法で割り当てる必要はありません。
また、使用する waveOut API 呼び出しがどれも失敗しないようにしています。
現在のところ、これは Windows のバグか、オーディオ ドライバのバグであると推測しています。反対意見はいつでも大歓迎です。