microsoft / microsoft/windows-rs

`windows-bindgen` Owned callbacks for non-COM interfaces (XAudio2, SourceVoice)

Open
#4,668 1 comment 1 reaction 0 assignees View on GitHub
question
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.