microsoft / microsoft/windows-rs
`windows-bindgen` Owned callbacks for non-COM interfaces (XAudio2, SourceVoice)
- Dominant language
- Rust
- Stars
- 12.8k
- Forks
- 665
- Avg merge
- 7h 9m
- Merged PRs (30d)
- 70
Description
### Suggestion
Would it be possible to add `Callback::new` functions that take ownership of a callback implementation, rather than borrowing it? Eg:
```rs
impl IXAudio2VoiceCallback {
// Now T is boxed and owned by this wrapper
pub fn new_owned(this: T) -> windows_core::NonScopedInterface { }
}
```
Specifically, `IXAudio2SourceVoice` callbacks MUST outlive the source voice. `IXAudio2SourceVoice::Start` is asynchronous, and returns before playback has finished.
And the only way to figure out if the sound is finished is:
- either by periodically fetching the state (`IXAudio2SourceVoice::GetState`)
- or by getting a notification via `IXAudio2VoiceCallback::OnBufferEnd` callback, from which we would normally either destroy or reuse that voice.
And the following is UB:
```rs
let callb = MyCallback;
let i_callb: ScopedInterface<'_, IXAudio2VoiceCallback> = IXAudio2VoiceCallback::new(&callb);
let mut source: Option = None;
ixaudio2.CreateSourceVoice(&mut source, pcallback: Some(i_callb.deref()), ...);
source.SubmitSourceBuffer(...)
source.Start(...)
return;
```
because we return while the source voice is still alive and expects the pointer to the vtable to be valid.
So the proper usage would be to instantiate the callback once at the very beginning, store and use it for each source voice creation:
```rs
struct XAudio2 {
ixaudio2: IXAudio2,
callback: NonScopedInterface,
}
impl XAudio2 {
pub fn new() -> Self {
// create IXAudio2, mastering voice, etc ...
let callback = MyCallback;
Self { ixaudio2, callback: IXAudio2VoiceCallback::new_owned(callback) }
}
pub fn play_sound(&self, path: ...) {
let mut source: Option = None;
self.ixaudio2.CreateSourceVoice(&mut source, ..., pcallback: Some(self.callback.deref()), ...);
source.SubmitSourceBuffer(...);
source.Start(...);
return;
}
}
```
With the current approach, storing both `MyCallback` and the `ScopedInterface` (which is tied to the callback's lifetime) inside a single struct would be a difficult challenge.
Contributor guide
Research direction
Start with the windows-bindgen generation of IXAudio2VoiceCallback::new and the NonScopedInterface and ScopedInterface types mentioned in the issue. Trace how callback lifetimes are represented for IXAudio2SourceVoice, then determine how owned constructors should cover the non-COM callback interfaces. Done means owned callbacks can safely outlive the source voice without requiring a separately stored borrowed implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- audio-video-rtc, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 43/100