How to implement RegisterEndpointNotificationCallback method through a class and pass ComboBox as argument on it, in win32?

MERUN KUMAR MAITY 511 Reputation points
2023-05-15T21:58:01.3933333+00:00

Hi, there I have an application where I use part of a Win32 API. The functionality of that API is to retrieve the available audio devices. But the problem is, it is unable to detect any devices changes at runtime, If I plugged in a new audio device. It's only possible to detect those changes if I restart my application.

To solve this issue, I want to implement RegisterEndpointNotificationCallback method in the part of my Win32 code. But the problem is I tried a lot but still some code issue is present.

Now, comes into the technical part that what I want. Actually, I want to shown the available audio devices in a ComboBox that means when the user click the ComboBox the drop down will open and the audio device list is shown there. My goal is, suppose the user plug in a new device at runtime then the new audio device should shown in the combobox drop down instead of restarting the application and the second one is, if the drop down is opened on the application and at that same time the new device is plugged in then what I want is the drop down should close immediately as soon as there any device changes happen and after reopening the drop down the newly plugged device should shown there, that's it.

Here is the full source code, where I implement RegisterEndpointNotificationCallback method in my default audio device code :

public partial class MainWindow : Window
    {
        public enum HRESULT : int
        {
            S_OK = 0,
            S_FALSE = 1,
            E_NOINTERFACE = unchecked((int)0x80004002),
            E_NOTIMPL = unchecked((int)0x80004001),
            E_FAIL = unchecked((int)0x80004005),
            E_UNEXPECTED = unchecked((int)0x8000FFFF)
        }

        [ComImport]
        [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IMMDeviceEnumerator
        {
            HRESULT EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, out IMMDeviceCollection ppDevices);
            // for 0x80070490 : Element not found
            [PreserveSig]
            HRESULT GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);
            HRESULT GetDevice(string pwstrId, out IMMDevice ppDevice);
            HRESULT RegisterEndpointNotificationCallback(IMMNotificationClient pClient);
            HRESULT UnregisterEndpointNotificationCallback(IMMNotificationClient pClient);
        }

        public const int DEVICE_STATE_ACTIVE = 0x1;
        public const int DEVICE_STATE_DISABLED = 0x2;
        public const int DEVICE_STATE_NOTPRESENT = 0x4;
        public const int DEVICE_STATE_UNPLUGGED = 0x8;
        public const int DEVICE_STATEMASK_ALL = 0xF;

        [ComImport]
        [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IMMDeviceCollection
        {
            HRESULT GetCount(out uint pcDevices);
            HRESULT Item(uint nDevice, out IMMDevice ppDevice);
        }

        [ComImport]
        [Guid("D666063F-1587-4E43-81F1-B948E807363F")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IMMDevice
        {
            HRESULT Activate(ref Guid iid, int dwClsCtx, ref PROPVARIANT pActivationParams, out IntPtr ppInterface);
            HRESULT OpenPropertyStore(int stgmAccess, out IPropertyStore ppProperties);
            HRESULT GetId(out IntPtr ppstrId);
            HRESULT GetState(out int pdwState);
        }

        [ComImport]
        [Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IMMNotificationClient
        {
            [PreserveSig]
            HRESULT OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState);
            [PreserveSig]
            HRESULT OnDeviceAdded(string pwstrDeviceId);
            [PreserveSig]
            HRESULT OnDeviceRemoved(string pwstrDeviceId);
            [PreserveSig]
            HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId);
            [PreserveSig]
            HRESULT OnPropertyValueChanged(string pwstrDeviceId, ref PROPERTYKEY key);
        }

        public enum EDataFlow
        {
            eRender = 0,
            eCapture = (EDataFlow.eRender + 1),
            eAll = (EDataFlow.eCapture + 1),
            EDataFlow_enum_count = (EDataFlow.eAll + 1)
        }

        public enum ERole
        {
            eConsole = 0,
            eMultimedia = (ERole.eConsole + 1),
            eCommunications = (ERole.eMultimedia + 1),
            ERole_enum_count = (ERole.eCommunications + 1)
        }

        public struct PROPERTYKEY
        {
            public PROPERTYKEY(Guid InputId, uint InputPid)
            {
                fmtid = InputId;
                pid = InputPid;
            }

            private Guid fmtid;
            private uint pid;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROPARRAY
        {
            public uint cElems;
            public IntPtr pElems;
        }

        [StructLayout(LayoutKind.Explicit, Pack = 1)]
        public struct PROPVARIANT
        {
            [FieldOffset(0)]
            public ushort varType;
            [FieldOffset(2)]
            public ushort wReserved1;
            [FieldOffset(4)]
            public ushort wReserved2;
            [FieldOffset(6)]
            public ushort wReserved3;
            [FieldOffset(8)]
            public byte bVal;
            [FieldOffset(8)]
            public sbyte cVal;
            [FieldOffset(8)]
            public ushort uiVal;
            [FieldOffset(8)]
            public short iVal;
            [FieldOffset(8)]
            public UInt32 uintVal;
            [FieldOffset(8)]
            public Int32 intVal;
            [FieldOffset(8)]
            public UInt64 ulVal;
            [FieldOffset(8)]
            public Int64 lVal;
            [FieldOffset(8)]
            public float fltVal;
            [FieldOffset(8)]
            public double dblVal;
            [FieldOffset(8)]
            public short boolVal;
            [FieldOffset(8)]
            public IntPtr pclsidVal;
            [FieldOffset(8)]
            public IntPtr pszVal;
            [FieldOffset(8)]
            public IntPtr pwszVal;
            [FieldOffset(8)]
            public IntPtr punkVal;
            [FieldOffset(8)]
            public PROPARRAY ca;
            [FieldOffset(8)]
            public System.Runtime.InteropServices.ComTypes.FILETIME filetime;
        }

        [ComImport]
        [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IPropertyStore
        {
            HRESULT GetCount(out uint propertyCount);
            HRESULT GetAt([In] uint propertyIndex, [MarshalAs(UnmanagedType.Struct)] out PROPERTYKEY key);
            HRESULT GetValue([In][MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [MarshalAs(UnmanagedType.Struct)] out PROPVARIANT pv);
            HRESULT SetValue([In][MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [In][MarshalAs(UnmanagedType.Struct)] ref PROPVARIANT pv);
            HRESULT Commit();
        }

        public const int STGM_READ = 0x0;
        public const int STGM_WRITE = 0x1;
        public const int STGM_READWRITE = 0x2;


        public class CEndPointMonitor : IMMNotificationClient, IDisposable
        {
            public CEndPointMonitor()
            {
                Initialize();
            }

            IMMDeviceEnumerator m_pMMDeviceEnumerator = null;
            bool m_bRegisteredForEndpointNotifications = false;
            private bool disposedValue;

            public HRESULT Initialize()
            {
                HRESULT hr = HRESULT.S_OK;
                Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}");
                object MMDeviceEnumerator = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true));
                m_pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;
                hr = m_pMMDeviceEnumerator.RegisterEndpointNotificationCallback(this);
                if (hr == HRESULT.S_OK)
                    m_bRegisteredForEndpointNotifications = true;
                return hr;
            }

            HRESULT IMMNotificationClient.OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState)
            {
                // Add code
                return HRESULT.S_OK;
            }

            HRESULT IMMNotificationClient.OnDeviceAdded(string pwstrDeviceId)
            {
                // Add code

 List<AudioDevice> devices = new();          
            HRESULT hr = HRESULT.E_FAIL;
                Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}");
                Type MMDeviceEnumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true);
                object MMDeviceEnumerator = Activator.CreateInstance(MMDeviceEnumeratorType);
            IMMDeviceEnumerator pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;
                if (pMMDeviceEnumerator != null)
                {
                    string sIdDefaultRender = null;
                    string sIdDefaultCapture = null;
                    IMMDevice pDefaultDevice = null;
                    hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out pDefaultDevice);

                m_pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;
                if (hr == HRESULT.S_OK)
                    {
                        IntPtr hGlobal = Marshal.AllocHGlobal(260);
                        hr = pDefaultDevice.GetId(out hGlobal);
                        sIdDefaultRender = Marshal.PtrToStringUni(hGlobal);
                        Marshal.FreeHGlobal(hGlobal);
                        Marshal.ReleaseComObject(pDefaultDevice);
                    }
                    hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eConsole, out pDefaultDevice);
                    if (hr == HRESULT.S_OK)
                    {
                        IntPtr hGlobal = Marshal.AllocHGlobal(260);
                        hr = pDefaultDevice.GetId(out hGlobal);
                        sIdDefaultCapture = Marshal.PtrToStringUni(hGlobal);
                        Marshal.FreeHGlobal(hGlobal);
                        Marshal.ReleaseComObject(pDefaultDevice);
                    }
                    IMMDeviceCollection pDeviceCollection = null;
                    hr = pMMDeviceEnumerator.EnumAudioEndpoints(EDataFlow.eAll, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, out pDeviceCollection);
                    if (hr == HRESULT.S_OK)
                    {
                        uint nDevices = 0;
                        hr = pDeviceCollection.GetCount(out nDevices);
                        devices.Add(new AudioDevice() { Name = "System default", Direction = "Playback", Id = sIdDefaultRender, Default = true });
                        for (uint i = 0; i < nDevices; i++)
                        {
                            IMMDevice pDevice = null;
                            hr = pDeviceCollection.Item(i, out pDevice);
                            if (hr == HRESULT.S_OK)
                            {
                                IPropertyStore pPropertyStore = null;
                                hr = pDevice.OpenPropertyStore(STGM_READ, out pPropertyStore);
                                if (hr == HRESULT.S_OK)
                                {
                                    string sFriendlyName = null;
                                    string sDesc = null;
                                    PROPVARIANT pv = new PROPVARIANT();
                                    hr = pPropertyStore.GetValue(ref PKEY_Device_FriendlyName, out pv);
                                    if (hr == HRESULT.S_OK)
                                    {
                                        sFriendlyName = Marshal.PtrToStringUni(pv.pwszVal);
                                    }
                                    hr = pPropertyStore.GetValue(ref PKEY_Device_DeviceDesc, out pv);
                                    if (hr == HRESULT.S_OK)
                                    {
                                        sDesc = Marshal.PtrToStringUni(pv.pwszVal);
                                    }
                                    IntPtr hGlobal = Marshal.AllocHGlobal(260);
                                    hr = pDevice.GetId(out hGlobal);
                                    string sId = Marshal.PtrToStringUni(hGlobal);
                                    Marshal.FreeHGlobal(hGlobal);
                                    IMMEndpoint pEndpoint = null;
                                    pEndpoint = (IMMEndpoint)pDevice;
                                    EDataFlow eDirection = EDataFlow.eAll;
                                    hr = pEndpoint.GetDataFlow(out eDirection);
                                    //System.Diagnostics.Trace.WriteLine("\tDirection : " + eDirection.ToString());
                                    string sDirection = "";
                                    if (eDirection == EDataFlow.eRender)
                                        sDirection = "Playback";
                                    else if (eDirection == EDataFlow.eCapture)
                                        sDirection = "Recording";
                                    int nState = 0;
                                    hr = pDevice.GetState(out nState);
                                    if ((nState == DEVICE_STATE_ACTIVE))
                                    {
                                        devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Id = sId, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                                        //sFriendlyName += (sId == sIdDefaultRender || sId == sIdDefaultCapture) ? " (System default)" : "";
                                        //devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                                        ////devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });
                                    }
                                    Marshal.ReleaseComObject(pPropertyStore);
                                }
                                Marshal.ReleaseComObject(pDevice);
                            }
                        }
                        devices.Insert(devices.Count - 0, new AudioDevice() { Name = "Selected application", Direction = "Recording", Id = "Idlast", Default = false });
                    }
                    Marshal.ReleaseComObject(pDeviceCollection);
                ListCollectionView lcv = new(devices);
                lcv.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));
                ObservableCollection<AudioDevice> lcv2 = new(devices);
                

              cmb1.ItemsSource = lcv2; // Problem is here
            
}
        }
                return HRESULT.S_OK;
            }

            HRESULT IMMNotificationClient.OnDeviceRemoved(string pwstrDeviceId)
            {
                // Add code
                return HRESULT.S_OK;
            }

            HRESULT IMMNotificationClient.OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId)
            {
                // Add code
                return HRESULT.S_OK;
            }

            HRESULT IMMNotificationClient.OnPropertyValueChanged(string pwstrDeviceId, ref PROPERTYKEY key)
            {
                // Add code
                return HRESULT.S_OK;
            }

            protected virtual void Dispose(bool disposing)
            {
                if (!disposedValue)
                {
                    if (disposing)
                    {
                        // TODO: dispose managed state (managed objects)
                    }

                    // TODO: free unmanaged resources (unmanaged objects) and override finalizer
                    // TODO: set large fields to null

                    if (m_pMMDeviceEnumerator != null)
                    {
                        if (m_bRegisteredForEndpointNotifications)
                            m_pMMDeviceEnumerator.UnregisterEndpointNotificationCallback(this);
                        Marshal.ReleaseComObject(m_pMMDeviceEnumerator);
                    }
                    disposedValue = true;
                }
            }

            // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
            ~CEndPointMonitor()
            {
                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
                Dispose(disposing: false);
            }

            void IDisposable.Dispose()
            {
                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
                Dispose(disposing: true);
                GC.SuppressFinalize(this);
            }
        }


        public MainWindow()
        {
            InitializeComponent();
        }

        CEndPointMonitor epm = null;

       

        private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (epm != null)
                ((IDisposable)epm).Dispose();           
        }
    }

If you carefully see the code, then you can definitely see in the HRESULT IMMNotificationClient.OnDeviceAdded block, the last line is cmb1.ItemsSource = lcv2;

this is where I face the issue and I also commented there as "Problem is here". Because I do all coding to retrieve the default audio devices in a separate class name CEndPointMonitorthat's why it's unable to recognize the Combo box name cmb1, this is not a big deal when my retrieving Audio device code is under the Main class on that situation the Combo box name cmb1 is automatically recognize by the application because it's part of Mainwindow xaml UI component.

My first question is, how can I pass the ComboBox as argument of CEndPointMonitor class.

Now comes into the second part, as I say previously if the code is under Main class then it's easier to manipulate the code or calling the code and get the audio device list and put the result in a Listcollectionview and add it in a ComboBox but now it is not in the Main class. instead of it is inside CEndPointMonitor class. To call this CEndPointMonitor class we need to create an instance of this class something like epm = new CEndPointMonitor();but I don't know how to do that.

My second question is, how can I initialize the CEndPointMonitor class to get the Listcollection view of the audio devices List on ComboBox dropdown open, basically the audio device list should be refreshed each time I open the ComboBox drop down.

Windows 10
Windows 10
A Microsoft operating system that runs on personal computers and tablets.
10,774 questions
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,686 questions
Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,442 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,362 questions
{count} votes