cpal
cpal copied to clipboard
fix: ASIO Segfault on Windows
Commit Description
This commit fixes a bug that occurs in the ASIO host on windows. It commonly manifests in a message about Data Execution Prevention. From what I can tell, the ASIO API call ASIOCreateBuffers() expects the callbacks struct to exist for the lifetime of the buffers (i.e. until ASIOFreeBuffers() is called). This isn't really mentioned in the ASIO Docs unfortunately, and I only found it by hunting for this bug.
This commit fixes the issue by making the AsioCallbacks struct a static object rather than a local that is constructed by a function. It has to be mutable, unfortunately, because the C api for ASIOCreateBuffers() asks for a non-const pointer. I can't think of any reason that it would be mutated by the driver, so I still think this is the best solution.
Further Elaboration
I initially fixed this bug by changing the code which calls ASIOCreateBuffers to look like this:
fn create_buffers(
&self,
buffer_infos: &mut [AsioBufferInfo],
buffer_size: Option<i32>,
) -> Result<c_long, AsioError> {
let num_channels = buffer_infos.len();
// To pass as ai::ASIOCallbacks
let callbacks = create_asio_callbacks();
let callbacks = Box::new(callbacks); // CHANGE
let mut state = self.inner.lock_state();
// ... snip ...
unsafe {
asio_result!(ai::ASIOCreateBuffers(
buffer_infos.as_mut_ptr() as *mut _,
num_channels as i32,
buffer_size,
Box::leak(callbacks) as *mut _ as *mut _, // CHANGE
))?;
}
*state = DriverState::Prepared;
Ok(buffer_size)
}
And it stopped giving me the access violation. I'm fairly sure this PR would address #596 #539 #407
I can confirm, this has removed the access violation crashes I was experiencing with Tascam US16x08 with their stock ASIO driver on windows 10/11 with cpal. The same soundcard worked fine (ie. without the access violation crash) with ASIO4ALL. I think this confirms that the bug occurs because some drivers do something weird with the Callbacks struct.
I have found one access violation crash that this doesn't fix. I've observed that when you create two Device
handles to the same device (for example by calling default_input_device
and default_output_device
and they happen to be the same device) and then you build input stream on one and output stream on the other, the access violation crash still happens. The crash goes away, if you use the same Device
object to build both input and output streams. I think this constitutes unsoundness in the API, since it means Device
is not always safe to use.
Closing as we have #775