windows-rs: IMMDevice.Activate fails with IAudioSessionManager2

Hello, thank you for this amazing library,

I get an error while retrieving back a pointer that should be filled by the IMMDevice::Activate function.

Exception 0xc0000005 encountered at address 0x7ff64e1d65e7: Access violation reading location 0xffffffffffffffff
(exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

I have got this error on Windows 10 with the following configuration:

  • cargo 1.53.0 (4369396ce 2021-04-27)
  • rustc 1.53.0 (53cb7b09b 2021-06-17)
  • toolchain: stable-x86_64-pc-windows-msvc

Here is a minimal (non!)working example. Code is commented to highlight the problem.

Cargo.toml

[package]
name = "my-package"
version = "0.1.0"
authors = ["spineki"]
edition = "2018"

[dependencies.windows]
version = "0.28.0"
features = [
  "Win32_Foundation",
  "Win32_Media_Audio",
  "Win32_Media_Multimedia",
  "Win32_System_Com",
  "Win32_System_Com_StructuredStorage",
]

Code:

use windows::Win32::{
    Media::Audio::{
        eCapture, eMultimedia, IAudioSessionManager2, IMMDeviceEnumerator, MMDeviceEnumerator,
    },
    System::Com::{
        CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, CLSCTX_INPROC_SERVER,
        COINIT_APARTMENTTHREADED,
    },
};

use windows::core::{Interface, GUID};

fn main() {
    unsafe {
        CoInitializeEx(std::ptr::null(), COINIT_APARTMENTTHREADED).expect("CoInitializeEx Failed");

        // Getting the device enumerator: works
        let imm_device_enumerator: IMMDeviceEnumerator =
            CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_INPROC_SERVER)
                .expect("CoCreateInstance Failed");

        // Getting the IMMDevice of the defaultAudioEndpoint: works
        let endpoint = imm_device_enumerator
            .GetDefaultAudioEndpoint(eCapture, eMultimedia)
            .expect("GetDefaultAudioEnpoint Failed");

        // preparing a pointer that will store the address of the created IAudioSessionManager2
        let mut pinterface: *mut std::ffi::c_void = std::ptr::null_mut();

        // Activating: the target Interface is IAudioSessionManager2: No error!
        endpoint
            .Activate(
                &IAudioSessionManager2::IID,
                CLSCTX_ALL.0,
                std::ptr::null_mut(),
                &mut pinterface as *mut _,
            )
            .expect("Activate Failed");

        // -> Reaching this point, so Activate did not failed

        // Casting back to IAudioSessionManager2: works
        let audio_session_enumerator_ref = (pinterface as *mut IAudioSessionManager2)
            .as_ref()
            .expect("Pointer to ref failed");


        // -------------------- HERE IS THE PROBLEM-------------------- 
        // The call to GetSessionEnumerator fails.
        let audio_session_enumerator = audio_session_enumerator_ref
            .GetSessionEnumerator()
            .expect("GetSessionEnumerator Failed");
        CoUninitialize();
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 26 (12 by maintainers)

Most upvoted comments

_COM_Outptr_ parameters must set the output value to null if they return failure. This is alluded to in the documentation where it says:

The returned pointer has COM semantics, which is why it carries an _On_failure_ post-condition that the returned pointer is null.

In sal.h it says:

// Annotations for _Outptr_ parameters which return a pointer to a ref-counted COM object,
// following the COM convention of setting the output to NULL on failure.
// The current implementation is identical to _Outptr_result_nullonfailure_.
// For pointers to types that are not COM objects, _Outptr_result_nullonfailure_ is preferred.

These DXC functions are returning pointers to interfaces that follow COM semantics, and so _COM_Outptr_ can be used here. The definitions in sal.h are also useful to try and figure out what these annotations mean:

#define _COM_Outptr_                        _SAL2_Source_(_COM_Outptr_, (),                      _Outptr_                      _On_failure_(_Deref_post_null_))
#define _COM_Outptr_result_maybenull_       _SAL2_Source_(_COM_Outptr_result_maybenull_, (),     _Outptr_result_maybenull_     _On_failure_(_Deref_post_null_))
#define _COM_Outptr_opt_                    _SAL2_Source_(_COM_Outptr_opt_, (),                  _Outptr_opt_                  _On_failure_(_Deref_post_null_))
#define _COM_Outptr_opt_result_maybenull_   _SAL2_Source_(_COM_Outptr_opt_result_maybenull_, (), _Outptr_opt_result_maybenull_ _On_failure_(_Deref_post_null_))

_COM_Outptr_ must be passed in as a non-null value, and if the function succeeds it will be set to a value and if it fails it will be set to null.

_COM_Outptr_opt_ may be null. If it is non-null then it behaves as for _COM_Outptr_.

The maybenull versions are as above, but the function is allowed to succeed and set the parameter to null.

I suggest you ask on the dxc repo about when they’re next planning to update the SDK with the latest DXC changes.

Generally, COM out params will return ownership of a ref-counted object. You should prefer to do something like this:

let mut pinterface: Option<IAudioSessionManager2> = None;

endpoint.Activate(
    &IAudioSessionManager2::IID,
    CLSCTX_ALL.0,
    std::ptr::null_mut(),
    &mut pinterface as *mut _ as _)
    .expect("Activate Failed");

Now the pinterface will own the resulting interface pointer (if any). It is regrettable that the GUID*, void** pair aren’t at the back of the parameter list. In such cases, the method is much easier to call because the Windows crate will take care of the binding automatically. I may ask the win32 metadata folks about making this easier.

Ideed. Thank you so much @MarijnS95 !

For future readers that didn’t find an example online like me before,

here is what it looks like

Before

let audio_session_enumerator_ref = (pinterface as *mut IAudioSessionManager2)
    .as_ref()
    .expect("Pointer to ref failed");


// -------------------- HERE IS THE PROBLEM-------------------- 
// The call to GetSessionEnumerator fails.
let audio_session_enumerator = audio_session_enumerator_ref
    .GetSessionEnumerator()
    .expect("GetSessionEnumerator Failed");

After

let audio_session_enumerator_ref =
    std::mem::transmute::<*mut std::ffi::c_void, IAudioSessionManager2>(pinterface);

// The call to GetSessionEnumerator now works!!
let audio_session_enumerator = audio_session_enumerator_ref
    .GetSessionEnumerator()
    .expect("GetSessionEnumerator Failed");

Works like a charm, I can call for example

let count = audio_session_enumerator.GetCount();

println!("count {:?}", count);

I think perhaps if the void** parameter had the ComOutPtr attribute (which this method currently lacks) I could then just walk backward and look for a GUID* parameter instead of expecting it to be the second-to-last. I’d have to experiment to see how reliable that is.

Agree, thought of that too when I was building the DIA sample that has a NoRegCoCreate workflow. Am also concerned about false positives, we’d have to take a closer look.

e.g.

HRESULT STDMETHODCALLTYPE NoRegCoCreate(
  const __wchar_t *dllName,
  REFCLSID   rclsid,
  REFIID     riid,
  void     **ppv);

@MarijnS95 yes, the metadata is missing the attribute as well, but it is also affected by the fact that the GUID parameter isn’t the second-to-last, as is conventional.