PCにマイクやスピーカー(オーディオデバイス)を抜きさししたときのイベントを取る(C#版)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

以前の記事で、C++で、オーディオデバイス(マイクとかスピーカーとか)が接続されたときのイベントを拾うということをした。

その時のコードを、C++からC#にしたい。

実験コード

COMの練習でやったように、COMのインターフェースやCoClassを取り込む。

それ以外は、やることはC++版と同じ。

using System.Runtime.InteropServices;

namespace MicJikkenCs
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            var pEnum = new MMDeviceEnumerator();
            var pNotifClient = new CMMNotificationClient();

            if (pEnum is IMMDeviceEnumerator p)
            {
                var HResult = p.RegisterEndpointNotificationCallback(pNotifClient);
            }

            Console.ReadLine();
        }
    }
}

[ComImport]
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
internal class MMDeviceEnumerator
{
}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
internal interface IMMDeviceEnumerator
{
    [PreserveSig]
    public void dummy1();
    [PreserveSig]
    public void dummy2();
    [PreserveSig]
    public void dummy3();
    [PreserveSig]
    public int RegisterEndpointNotificationCallback(IMMNotificationClient pClient);
}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")]
internal interface IMMNotificationClient
{
    [PreserveSig]
    int OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState);
    [PreserveSig]
    int OnDeviceAdded(string pwstrDeviceId);
    [PreserveSig]
    int OnDeviceRemoved(string pwstrDeviceId);
    [PreserveSig]
    int OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId);
    [PreserveSig]
    int 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 struct PROPERTYKEY
{
    public PROPERTYKEY(Guid InputId, uint InputPid)
    {
        fmtid = InputId;
        pid = InputPid;
    }

    private Guid fmtid;
    private uint pid;
}

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

public class CMMNotificationClient : IMMNotificationClient
{
    public int OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState)
    {
        Console.WriteLine(pwstrDeviceId);
        Console.WriteLine("");
        return 0;
    }

    // ★以下のメソッドは今回は使わないが、実装はしておかないと異常終了しちゃう

    public int OnDeviceAdded(string pwstrDeviceId)
    {
        return 0;
    }

    public int OnDeviceRemoved(string pwstrDeviceId)
    {
        return 0;
    }

    public int OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId)
    {
        return 0;
    }

    public int OnPropertyValueChanged(string pwstrDeviceId, ref PROPERTYKEY key)
    {
        return 0;
    }
}

ポイント

COMの練習で、COMのインターフェースを取り込むときに、「使わないメソッドは真面目に書かなくてよい」と言っていたが、今回の、抜きさしイベントを拾うためのIMMNotificationClientは、持ってるメソッドを全部書かないとダメ。

今回の抜きさし検出に使うのはOnDeviceStateChanged()だけだったので

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")]
internal interface IMMNotificationClient
{
    [PreserveSig]
    int OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState);
}

だけ書いて、そのインターフェースを実装するクラスの方も、

public class CMMNotificationClient : IMMNotificationClient
{
    public int OnDeviceStateChanged(string pwstrDeviceId, uint dwNewState)
    {
        Console.WriteLine(pwstrDeviceId);
        Console.WriteLine("");
        return 0;
    }
}

だけ書いていたのだが、それだとイベント発生時になにかエラーが起きて、アプリが終了してしまった。(どんなエラーだったかまでは見てない)

おそらく、イベント発生したときに、呼ぶべきメソッドが実装されてないので、エラーが起きたのかな?と思われる。

IMMNotificationClientインターフェースが持っている全メソッドを定義して、実装するクラスでも全メソッドを実装したら、イベント発生時に各ハンドラを通ってくれるようになった。

参考

PCにマイクやスピーカー(オーディオデバイス)を抜きさししたときのイベントを取る(C++

https://tera1707.com/entry/2023/10/11/205400

How to implement RegisterEndpointNotificationCallback method through a class and pass ComboBox as argument on it, in win32?
ここのコードをだいぶ参考にさせてもらった。ありがとうございます。

https://learn.microsoft.com/en-us/answers/questions/1285460/how-to-implement-registerendpointnotificationcallb