The call to DllGetClassObject() doesn't return an IDirectSound interface, it returns an IClassFactory interface capable of creating an IDirectSound interface. It just so happened that CreateSoundBuffer() has the same position in the vtable and the same number of bytes of parameters and same return type as the class factory's create interface method.
So, it now all works. Sort of. Well, when I say it "works", I mean it doesn't crash, and I can see the DirectSound then DirectSound buffer interface created as you'd expect. And yes, QMixer does try to create a hardware buffer. And I intercept it and make it a software buffer.
However, that doesn't seem to help very much. I think it makes a small difference, but I might just be imagining things. The problem is that QMixer doesn't use notifications, it uses GetCurrentPosition(). And I can only assume that it's screwing up somehow.
So, the next thing to try will be stuffing several notifications into the buffer, and using the last one hit as the return value for GetCurrentPosition().
Slightly off-topic; QMixer is just horrible. It creates a looping sound buffer for every sample played, and seems to fill it pretty much constantly, then releases it once the sample has played. Why on earth it doesn't recycle buffers, I have no idea...