Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify non-COM interface parameter bindings #2098

Open
stuntman11 opened this issue Oct 16, 2022 · 5 comments
Open

Simplify non-COM interface parameter bindings #2098

stuntman11 opened this issue Oct 16, 2022 · 5 comments
Labels
enhancement New feature or request

Comments

@stuntman11
Copy link

Hello, the last couple of days I tried to migrate my XAudio2 project from C++ to Rust and was simply unable to implement the IXAudio2VoiceCallback interface.

I am quite new to the rust language and even after hours of research do not understand why the implement macro doesn't work in my case. I don't think this is a general rust question because the macro is a specific functionality of the windows crate.


I want to call the function CreateSourceVoice on an XAudio2 instance:

audio.CreateSourceVoice(&mut p_source_voice, &format, 0, 2.0, &audio_callback.into(), Some(&music_voice_sends), None)

To create an implementation for the IXAudio2VoiceCallback interface I use the implement macro:

#[implement(IXAudio2VoiceCallback)]
struct MyCallbacks {}

#[allow(non_snake_case)]
impl IXAudio2VoiceCallback_Impl for MyCallbacks {
    fn OnVoiceProcessingPassStart(&self, bytesrequired:u32) {}
    fn OnVoiceProcessingPassEnd(&self) {}
    fn OnStreamEnd(&self) {}
    fn OnBufferStart(&self, pbuffercontext: *mut core::ffi::c_void) {}
    fn OnBufferEnd(&self, pbuffercontext: *mut core::ffi::c_void) {}
    fn OnLoopEnd(&self, pbuffercontext: *mut core::ffi::c_void) {}
    fn OnVoiceError(&self, pbuffercontext: *mut core::ffi::c_void, error: windows::core::HRESULT) {}
}

The compiler error for the implement macro is:

this associated function takes 1 generic argument but 3 generic arguments were supplied
expected 1 generic argumentrustc[E0107](https://doc.rust-lang.org/error-index.html#E0107)
impl.rs(646, 18): associated function defined here, with 1 generic parameter: `Impl`
main.rs(14, 1): remove these generic arguments
the trait bound `IXAudio2VoiceCallback: RuntimeName` is not satisfied
the following other types implement trait `RuntimeName`:
  IActivateAudioInterfaceAsyncOperation
  IActivateAudioInterfaceCompletionHandler
  IAudioAmbisonicsControl
  IAudioAutoGainControl
  IAudioBass
  IAudioCaptureClient
  IAudioChannelConfig
  IAudioClient
and 77 othersrustc[E0277](https://doc.rust-lang.org/error-index.html#E0277)
inspectable.rs(134, 52): required by a bound in `IInspectable_Vtbl::new`
no function or associated item named `matches` found for struct `IXAudio2VoiceCallback_Vtbl` in the current scope
function or associated item not found in `IXAudio2VoiceCallback_Vtbl`rustc[E0599](https://doc.rust-lang.org/error-index.html#E0599)

The issue "How to get COM object as "base" Interface without move" shows a similar situation with the IAudioEndpointVolumeCallback interface but in their case it just works. It would be awesome if someone with more windows crate knowledge or more rust experience in generell could help me.

@kennykerr kennykerr added the question Further information is requested label Oct 16, 2022
@kennykerr
Copy link
Collaborator

Hey, that's a confusing error! Getting Rust macros to produce meaningful errors is hard... Anyway, the trick is that the implement macro is only for COM interfaces and IXAudio2VoiceCallback is not a COM interface! A few Windows APIs rely on these Frankenstein interfaces that don't inherit from IUnknown and thus have no way to express lifetime and discovery.

The good news is that I recently provided support for such interfaces: #2066

@kennykerr kennykerr changed the title How to implement IXAudio2VoiceCallback interface How to implement non-COM interfaces Oct 17, 2022
@stuntman11
Copy link
Author

stuntman11 commented Oct 17, 2022

I applied the concepts from the example you provided in (#2066) to my XAudio2 problem and think it works. Now I use the interface macro to declare my own type trait for the IXAudio2VoiceCallback interface. But I have a follow-up question regarding the interface instantiation and to simplify the example I only show the OnBufferEnd method.

struct XAudio2VoiceCallbacks;

#[interface]
unsafe trait XAudio2VoiceCallbackInterface {
    unsafe fn OnBufferEnd(&self, pbuffercontext: *mut ::core::ffi::c_void);
}

impl XAudio2VoiceCallbackInterface_Impl for XAudio2VoiceCallbacks {
    unsafe fn OnStreamEnd(&self) {}
}

The final goal is to call the CreateSourceVoice() method which expects an Into<InParam<'a, IXAudio2VoiceCallback>> for the callbacks argument. At first I thought there must be some kind of constructor or conversion method to convert my callbacks struct to the official callbacks struct but didn't find anything. So do I use mem::transmute() to convert between the types?

let callback_instance = XAudio2VoiceCallbacks {};
let callback_interface = XAudio2VoiceCallbackInterface::new(&callback_instance);
let callback_windows = mem::transmute(callback_interface);

Thanks for your time! I am still in the process of learning the rust language and really appreciate the support.

@kennykerr
Copy link
Collaborator

No, since the IXAudio2VoiceCallback interface is defined by the windows crate, you need not use the interface macro to define it again. That macro exists only for those interfaces that aren't already defined by the windows crate.

So I don't know anything about the XAudio2 API but something like this should get you started:

[dependencies.windows]
version = "0.42"
features = [
    "implement",
    "Win32_Foundation",
    "Win32_Media_Audio_XAudio2",
    "Win32_System_Com",
    "Win32_System_SystemInformation",
]
use windows::{
    core::*, Win32::Media::Audio::XAudio2::*, Win32::System::Com::*,
    Win32::System::SystemInformation::*,
};

struct Callback;

impl IXAudio2VoiceCallback_Impl for Callback {
    fn OnVoiceProcessingPassStart(&self, _: u32) {
        todo!()
    }
    fn OnVoiceProcessingPassEnd(&self) {
        todo!()
    }
    fn OnStreamEnd(&self) {
        println!("callback");
    }
    fn OnBufferStart(&self, _: *mut std::ffi::c_void) {
        todo!()
    }
    fn OnBufferEnd(&self, _: *mut std::ffi::c_void) {
        todo!()
    }
    fn OnLoopEnd(&self, _: *mut std::ffi::c_void) {
        todo!()
    }
    fn OnVoiceError(&self, _: *mut std::ffi::c_void, _: HRESULT) {
        todo!()
    }
}

fn main() -> Result<()> {
    unsafe {
        CoInitializeEx(None, COINIT_MULTITHREADED)?;

        let mut audio = None;
        XAudio2CreateWithVersionInfo(&mut audio, 0, XAUDIO2_DEFAULT_PROCESSOR, NTDDI_VERSION)?;

        if let Some(audio) = audio {
            // Call the callback interface directly...
            let callback = IXAudio2VoiceCallback::new(&Callback);
            callback.OnStreamEnd();

           // Pass the callback to another API...
            let mut source = None;
            audio.CreateSourceVoice(
                &mut source,
                std::ptr::null(),
                0,
                0.0,
                &*callback,
                None,
                None,
            )?;
        }

        Ok(())
    }
}

Notice the weird &* that is required for complicated reasons. I hope to get rid of that soon.

Anyway, I hope that helps.

@stuntman11
Copy link
Author

Ohhhh wow the &* does the trick. Would never have guessed that. Thanks!

@kennykerr
Copy link
Collaborator

Great, I'll keep this open to remind me to get rid of the need for that trick.

@kennykerr kennykerr reopened this Oct 18, 2022
@kennykerr kennykerr added enhancement New feature or request and removed question Further information is requested labels Oct 18, 2022
@kennykerr kennykerr changed the title How to implement non-COM interfaces Simplify non-COM interface parameter bindings Oct 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants