MIDIポートを作成する
・midiOutOpenを使います。
その前に、まずMIDIポートの実装インターフェースを決めておきます。
MIDIポートに必要な機能は以下の通りです。
/**** Source Code (C#) ****/ public sealed class MidiOutPortHandle : IDisposable { // コンストラクタ public MidiOutPortHandle(int portNum) // デストラクタ ~MidiOutPortHandle() // IDisposable 実装 public void Dispose(); // ポートを閉じる public void Close(); // MIDIメッセージを送信する public void Send(byte[] data); // ポートの数を取得する public static int GetPortCount(); // ポートの情報を取得する public static MidiOutCapsA GetPortInformation(int portNum); // ポートの名前 public string Name { get; } }
アンマネージリソースを直接保有するインスタンスは、デストラクタを実装する必要があります。
IDisposableとデストラクタの違い
IDisposableもデストラクタもアンマネージリソースの解放のために使用することができますが、その使用目的は若干異なります。
IDisposableの目的はアンマネージリソースの保持を防止することであり、デストラクタの目的はアンマネージリソースの破棄を行うことです。IDisposableはMSDNのリファレンスにある通り、複数回の呼び出しを許可する実装でなくてはなりません。デストラクタはC言語と同様に、オブジェクトの破棄タイミングで確実に一度だけ呼び出されます。
IDisposableは抽象化された上位レイヤでも実装すべきですが、デストラクタはアンマネージリソースに関わる最下層でのみ実装します。usingブロックをステップアウトする際にはDispose関数が呼ばれますが、デストラクタの呼び出しタイミングはガベージコレクタの実装に依存します。
C#におけるデストラクタは、CLRのobject.Finalizeの実質のオーバーライドです。
詳細は以下の実装を参考にしてください。
コンストラクタの実装
midiOutCloseを実装するまで起動禁止です。
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力APIの宣言です。 /// </summary> public static class MidiOutApi { /// <summary> /// MIDI出力ポートを開きます。 /// </summary> [DllImport("winmm.dll", EntryPoint = "midiOutOpen")] [return: MarshalAs(UnmanagedType.U4)] public static extern MMResult midiOutOpen([MarshalAs(UnmanagedType.SysUInt)] ref IntPtr lphMidiOut, [MarshalAs(UnmanagedType.U4)] uint uDeviceID, [MarshalAs(UnmanagedType.FunctionPtr)] Delegate dwCallback, [MarshalAs(UnmanagedType.U4)] uint dwInstance, [MarshalAs(UnmanagedType.U4)] MidiPortOpenFlag dwFlags); }
省略可能な変数を省略すると以下のようになります。
/**** Source Code (C#) ****/ public static class MidiOutApi { /// <summary> /// MIDI出力ポートを開きます。 /// </summary> [DllImport("winmm.dll")] public static extern MMResult midiOutOpen(ref IntPtr lphMidiOut, uint uDeviceID, Delegate dwCallback, uint dwInstance, MidiPortOpenFlag dwFlags); }
第一引数から順に、ハンドルを格納する変数、ポートの番号、コールバック関数、インスタンスの情報、ポートの作成オプションです。
今回はコールバックは使いませんが、MIDI出力では送信完了時にコールバックを受け取ることができます。
コールバックについて
dwCallbackがコールバック関数、dwInstanceがコールバック関数に渡されるsenderだと考えることができます。
C#のイベントハンドラと違って、C++言語では関数とインスタンスを別々に扱うため、Delegate.Targetにあたるインスタンス情報を登録しておく必要があります。
作成オプションではコールバック関数についての詳細を示すことができますが、今回はデフォルト値を使用します。(MidiPortOpenFlagがMidiOutPortOpenFlagでない点に注目してください。この列挙型は、MIDI入力ポートを開く際に使用されます。)
MIDI出力ポートの実際のコンストラクタは以下の通りです。
・MidiOutPortHandle
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力ポートを抽象化するクラスです。 /// </summary> public sealed class MidiOutPortHandle : IDisposable { /// <summary> /// 指定した番号のポートを作成します。 /// </summary> public MidiOutPortHandle(int portNum) { name = GetPortInformation(portNum).szPname; MidiOutApi.midiOutOpen(ref hMidiOut, (uint)portNum, null, 0, MidiPortOpenFlag.NoCallback).Throw(); } IntPtr hMidiOut = IntPtr.Zero; bool disposed = false; string name; }
・MidiPortOpenFlag
/**** Source Code (C#) ****/ /// <summary> /// MIDIポートを開く時のオプションです。 /// </summary> public enum MidiPortOpenFlag : uint { /// <summary> /// コールバック機構を使用しません。 /// </summary> NoCallback = 0, /// <summary> /// コールバックはウィンドウメッセージとして送信されます。 /// </summary> CallbackWindow = 0x10000, /// <summary> /// コールバックはスレッドに送信されます。 /// </summary> CallbackThread = 0x20000, /// <summary> /// コールバックは関数ポインタです。 /// </summary> CallbackFunction = 0x30000 }
前のページで作成した拡張メソッドが使用されている点に注目してください。
デストラクタの実装
・MidiOutApi
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力APIの宣言です。 /// </summary> public static class MidiOutApi { /// <summary> /// MIDI出力ポートを閉じます。 /// </summary> [DllImport("winmm.dll", EntryPoint = "midiOutClose")] [return: MarshalAs(UnmanagedType.U4)] public static extern MMResult midiOutClose([MarshalAs(UnmanagedType.SysUInt)] IntPtr hMidiOut); }
・MidiOutPortHandle
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力ポートを抽象化するクラスです。 /// </summary> public sealed class MidiOutPortHandle : IDisposable { /// <summary> /// MIDIポートハンドルを解放します。 /// </summary> ~MidiOutPortHandle() { Release(true); } /// <summary> /// MIDIポートハンドルを開放します。 /// </summary> public void Close() { CheckDisposed(); Dispose(); } /// <summary> /// MIDIポートハンドルを開放します。 /// </summary> public void Dispose() { Release(false); } void Release(bool finalizing) { if (disposed) return; if (!finalizing) { GC.SuppressFinalize(this); } disposed = true; MidiOutApi.midiOutClose(hMidiOut).Throw(); } void CheckDisposed() { if (disposed) { throw new ObjectDisposedException(name); } } }
一般的なDispose、Finalizeの実装パターンとは違いますが、マネージリソースを使わないためこういう実装になっています。
Dispose関数を明示的に呼び出した場合はGC.SuppressFinalize関数を呼び出してFinalizeの実行を抑制しています。
以下に、送信コードを含まないMidiOutPortHandleのソースコードを書き下しておきます。
using System; using System.Runtime.InteropServices; using NextMidi.MidiPort; namespace NextMidi.MidiOut.Internal { /// <summary> /// MIDI出力ポートを抽象化するクラスです。 /// </summary> public sealed class MidiOutPortHandle : IDisposable { //----------// //----------// //----------// //----------// // // Constructor // //----------// //----------// //----------// //----------// /// <summary> /// 指定した番号のポートを作成します。 /// </summary> public MidiOutPortHandle(int portNum) { name = GetPortInformation(portNum).szPname; MidiOutApi.midiOutOpen(ref hMidiOut, (uint)portNum, null, 0, MidiPortOpenFlag.NoCallback).Throw(); } /// <summary> /// MIDIポートハンドルを解放します。 /// </summary> ~MidiOutPortHandle() { Release(true); } //----------// //----------// //----------// //----------// // // Variables // //----------// //----------// //----------// //----------// IntPtr hMidiOut = IntPtr.Zero; bool disposed = false; string name; //----------// //----------// //----------// //----------// // // Methods (IDisposable) // //----------// //----------// //----------// //----------// /// <summary> /// MIDIポートハンドルを開放します。 /// </summary> public void Close() { CheckDisposed(); Dispose(); } /// <summary> /// MIDIポートハンドルを開放します。 /// </summary> public void Dispose() { Release(false); } void CheckDisposed() { if (disposed) { throw new ObjectDisposedException(name); } } void Release(bool finalizing) { if (disposed) return; if (!finalizing) { GC.SuppressFinalize(this); } disposed = true; MidiOutApi.midiOutClose(hMidiOut).Throw(); } //----------// //----------// //----------// //----------// // // Methods // //----------// //----------// //----------// //----------// /// <summary> /// MIDI出力ポートの数を取得します。 /// </summary> public static int GetPortCount() { return (int)MidiOutApi.midiOutGetNumDevs(); } /// <summary> /// MIDI出力ポートの情報を取得します。 /// </summary> public static MidiOutCapsA GetPortInformation(int portNum) { var caps = new MidiOutCapsA(); MidiOutApi.midiOutGetDevCapsA((uint)portNum, ref caps, (uint)Marshal.SizeOf(typeof(MidiOutCapsA))).Throw(); return caps; } //----------// //----------// //----------// //----------// // // Properties // //----------// //----------// //----------// //----------// /// <summary> /// ポートの名前です。 /// </summary> public string Name { get { CheckDisposed(); return name; } } } }