Выяснил!
Всё оказалось предельно просто. Более того, оказалось, что для воспроизведения бага не нужно никаких контролов, ползунков громкости и панорамы — не нужно также ставить проект на паузу, ставить брекпоинты.
Достаточно просто во время работы проекта нажать кнопку «Стоп» — не важно, из выполняющегося ли проекта или из кода на паузе. Более того, падение остаётся и в скомпилированном проекте, если в случайный момент сделать
End.
Причина падения проста:Библиотека DirectSound нарушает фундаментальное правило COM в отношении времени жизни объектов (что время их жизни должно подчиняться подсчёту ссылок — пока хоть одна ссылка есть, объект не имеет права умирать).
Когда отпускается последняя ссылка на корневой объект иерархии — экзмпляр класса DirectSound8 — не только корневой объект уничтожается, но и дочерние, точнее подчинённые ему объекты (такие как объекты-буфры), на которые ещё можно быть полно действущих объектных ссылок,
ТОЖЕ уничтожаются. Не дожидаясь, пока ссылки на них будут корректно отпущены. Что нарушает правила COM.
Иными словами, одного
Set DS = Nothing достаточно для того, чтобы все остальные объекты, связанные с DirectSound, тоже уничтожились, а память, ими занимаемая, освободилась.
—Как это сталкивается с поведением VB?
—Очень просто! Вызов
RESDESCTBL::DestructModInst вызывается для экземпляра формы с целью зачистить все её внутренности. Этот метод обходит все члены «ModInst»-а и зачищает каждый вызовом
RESDESCTBL::DestructItem. Члены обходятся в том порядке, в каком они объявлены, а объявлены они в таком порядке:
- Код: Выделить всё
Dim DS As DirectSound8
Dim DSB As IDirectSoundBuffer8
Первой зануляется ссылка на корневой объект. Однако при этом (в нарушение правил) уничтожается не только корневой объект, но и все остальные.
Второй зануляется переменная
DSB — однако объект, ссылка на который всё ещё лежит в
DSB, уже уничтожился, память под него освобождена. Отсюда имеем креш.
Отсюда очевидный workaround для этого бага (со стороны библиотеки Direct Sound): поменять код так, чтобы при автоматической очистке памяти силами VB корневой объект освобождался последним их всех. То есть в данном случае просто объявить сначала
DSB, потом
DS.
Порядок обхода членов класса (формы) методом DestructModInst изменится, и объект-буфер не будет уничтожаться преждевременно.
Вот я сократил проект до той степени, когда баг проявляется и сделал две версии: как было (которая всегда должна падать при запуске), и с изменённым порядком переменных (которая не должна падать). Отличаются они
только порядком объявления переменных.
Майкл, пожалуйста, проверь и подтверди, что все мои выводы верны (а то вдруг у тебя какой-то другой баг выстреливает, кроме найденного мной сейчас).