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

Calling IMMDevice.Activate() results in a NullReferenceException #1081

Closed
pingzing opened this issue Oct 29, 2023 · 4 comments · Fixed by #1109
Closed

Calling IMMDevice.Activate() results in a NullReferenceException #1081

pingzing opened this issue Oct 29, 2023 · 4 comments · Fixed by #1109
Assignees
Labels
bug Something isn't working

Comments

@pingzing
Copy link

pingzing commented Oct 29, 2023

Actual behavior

Calling the IMMDevice.Activate() overload with the convenience extension (e.g. the one that takes a Guid rather than a Guid*) method results in a NullReferenceExcception.

It seems the issue is in the generated convenience extension method that looks like this:

internal static unsafe void Activate(this winmdroot.Media.Audio.IMMDevice @this, in global::System.Guid iid, winmdroot.System.Com.CLSCTX dwClsCtx, winmdroot.System.Com.StructuredStorage.PROPVARIANT ? pActivationParams, out object ppInterface)
{
	fixed (global::System.Guid* iidLocal = &iid)
	{
		winmdroot.System.Com.StructuredStorage.PROPVARIANT pActivationParamsLocal = pActivationParams ?? default(winmdroot.System.Com.StructuredStorage.PROPVARIANT );
		@this.Activate(iidLocal, dwClsCtx, pActivationParams.HasValue ? pActivationParamsLocal : Unsafe.NullRef<winmdroot.System.Com.StructuredStorage.PROPVARIANT >(), out ppInterface);
	}
}

The actual culprit is the Unsafe.NullRef<PROPVARIANT>() call--it seems that passing a non-nullable struct type to it results in a NullReferenceException.

Expected behavior

Calling the .Activate() convenience overload works!

Repro steps

  1. NativeMethods.txt content:
CoCreateInstance
MMDevice
MMDeviceEnumerator
IMMDeviceEnumerator
IAudioMeterInformation
  1. NativeMethods.json content (if present):
    N/A

  2. Any of your own code that should be shared?

Here's a little minimal sample, cut down a bit from what I have:

IAudioMeterInformation _audioMeterInfo;
HRESULT devEnumResult = PInvoke.CoCreateInstance(
    typeof(MMDeviceEnumerator).GUID,
    null,
    CLSCTX.CLSCTX_INPROC_SERVER,
    typeof(IMMDeviceEnumerator).GUID,
    out object rawDeviceEnumerator);

if (devEnumResult.Failed)
{
    Debug.WriteLine($"Failed to create a device enumerator: HRESULT {devEnumResult}");
    return;
}

IMMDeviceEnumerator? deviceEnumerator = (IMMDeviceEnumerator)rawDeviceEnumerator;
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out IMMDevice mmDevice);

// this following block *does* work.
unsafe
{
    Guid infoGuid = typeof(IAudioMeterInformation).GUID;
    Guid* iidLocal = &infoGuid;
    var nullPropvariant = new PROPVARIANT();
    mmDevice.Activate(iidLocal, CLSCTX.CLSCTX_ALL, nullPropvariant, out object rawMeterInfo);
    _audioMeterInfo = (IAudioMeterInformation)rawMeterInfo;
}

//mmDevice.Activate(typeof(IAudioMeterInformation).GUID, CLSCTX.CLSCTX_ALL, null, out object rawMeterInfo); // <-- this crashes!

Context

Note: this is a WPF project.

  • CsWin32 version: 0.3.49-beta
  • Win32Metadata version (if explicitly set by project): Not explicitly set
  • Target Framework: net7.0-windows
  • LangVersion (if explicitly set by project): [e.g. 9]: Not explicitly set
@pingzing pingzing added the bug Something isn't working label Oct 29, 2023
@riverar
Copy link

riverar commented Dec 30, 2023

I hit this as well. The nullable pActivationParams isn't being handled correctly here, ends up trying to access pActivationParams.HasValue.

Poking @AArnott for prioritization 😅

@AArnott AArnott self-assigned this Jan 4, 2024
@AArnott
Copy link
Member

AArnott commented Jan 9, 2024

I don't think there's anything wrong with calling pActivationParams.HasValue in the friendly overload, as that is a nullable struct, so it can't throw NRE.

I suspect the issue is that the .NET interop layer cannot handle a null reference passed into an in struct parameter on a COM interface. That seems unfortunate because the COM side should only need the address, and taking the address of a null reference is allowed in C# and produces a null pointer as intended.

But on the flip side, I don't know why CsWin32 is generating the COM interface with an in PROPVARIANT rather than PROPVARIANT* in the first place. Given it's an [Optional] parameter, CsWin32 should be (I think) be preferring pointers. That's the first thing I'll look into.

@AArnott
Copy link
Member

AArnott commented Jan 10, 2024

This bug was surprisingly difficult to solve.

@riverar
Copy link

riverar commented Jan 10, 2024

Yay, thanks @AArnott. Will try it out tonight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
3 participants