waveOutをIntPtrで動かす〜どうにかしてラッパーDLLなしでやりたい〜

以前,
http://d.hatena.ne.jp/HRM-7/20070125
で,C#上でwaveOutを動かしていたが,ラッパーDLLを使っていて,気になっていた.

とりあえず,

		#region DLL
		[DllImport("winmm.dll", EntryPoint = "waveOutOpen", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutOpen(HWAVEOUT__** phwo, uint uDeviceID, WAVEFORMATEX* pwfx, uint dwCallback, uint dwInstance, uint fdwOpen);

		[DllImport("winmm.dll", EntryPoint = "waveOutPrepareHeader", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutPrepareHeader(HWAVEOUT__* hwo, wavehdr_tag* pwh, uint cbwh);

		[DllImport("winmm.dll", EntryPoint = "waveOutWrite", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutWrite(HWAVEOUT__* hwo, wavehdr_tag* pwh, uint cbwh);

		[DllImport("winmm.dll", EntryPoint = "waveOutClose", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutClose(HWAVEOUT__* hwo);

		[DllImport("winmm.dll", EntryPoint = "waveOutReset", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutReset(HWAVEOUT__* hwo);

		[DllImport("winmm.dll", EntryPoint = "waveOutUnprepareHeader", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
		public static extern unsafe uint waveOutUnprepareHeader(HWAVEOUT__* hwo, wavehdr_tag* pwh, uint cbwh);

		[StructLayout(LayoutKind.Explicit, Size = 18)]
		public struct WAVEFORMATEX
		{
			[FieldOffset(0)]
			public ushort wFormatTag;

			[FieldOffset(2)]
			public ushort nChannels;

			[FieldOffset(4)]
			public uint nSamplesPerSec;

			[FieldOffset(8)]
			public uint nAvgBytesPerSec;

			[FieldOffset(12)]
			public ushort nBlockAlign;

			[FieldOffset(14)]
			public ushort wBitsPerSample;

			[FieldOffset(16)]
			public ushort cbSize;
		}

		[StructLayout(LayoutKind.Explicit, Size = 4)]
		public struct HWAVEOUT__
		{
			[FieldOffset(0)]
			public int unused;
		}

		[StructLayout(LayoutKind.Explicit, Size = 32)]
		public struct wavehdr_tag
		{
			[FieldOffset(0)]
			public unsafe sbyte* lpData;

			[FieldOffset(4)]
			public uint dwBufferLength;

			[FieldOffset(8)]
			public uint dwBytesRecorded;

			[FieldOffset(12)]
			public uint dwUser;

			[FieldOffset(16)]
			public uint dwFlags;

			[FieldOffset(20)]
			public uint dwLoops;

			[FieldOffset(24)]
			public unsafe wavehdr_tag* lpNext;

			[FieldOffset(28)]
			public uint reserved;
		}

		#endregion

		unsafe public static void Play(short[] wave_data, IntPtr hWnd)
		{
			//WAVEデバイス設定
			WAVEFORMATEX wf = new WAVEFORMATEX();
			wf.wFormatTag = 0x0001;     //PCM
			.......
			wf.cbSize = 0;              //PCMでは常に0
			HWAVEOUT__ hwo = new HWAVEOUT__();
			HWAVEOUT__* hWOut = &hwo;

			waveOutOpen(&hWOut, 0x0000, &wf, (uint)hWnd, 0, 0x00010000);

			//WAVE情報設定
			wavehdr_tag wt = new wavehdr_tag();
			fixed (short* tmp = &wave_data[0])
			{
				wt.lpData = (sbyte*)tmp;
				.....
				wt.reserved = 0;            //使用しない

				//再生
				waveOutPrepareHeader(hWOut, &wt, (uint)sizeof(wavehdr_tag));
				waveOutWrite(hWOut, &wt, (uint)sizeof(wavehdr_tag));
				System.Threading.Thread.Sleep(1000);
				waveOutReset(hWOut);
				waveOutUnprepareHeader(hWOut, &wt, (uint)sizeof(wavehdr_tag));
			}
			waveOutClose(hWOut);
		}

って感じで動くのだが,コールバックを受け取りたいので,OpenとPlayとCloseは分けたいなと.
だけども,
waveOutOpen(HWAVEOUT__** phwo
このポインタのポインタがくせもので,ちょっと困難.
とりあえず,IntPtrにしてみた.

waveOutOpen(IntPtr phwo
waveOutPrepareHeader(IntPtr hwo
...

それに伴い,

			HWAVEOUT__ hwo = new HWAVEOUT__();
			HWAVEOUT__* hWOut = &hwo;
			IntPtr ptr2 = (IntPtr)(&hWOut);
			waveOutOpen(ptr2, 0x0000, &wf, (uint)hWnd, 0, 0x00010000);

ただ,ここで,ポインタのポインタってポインタの4バイト小さいだけらしい!?ので,

			HWAVEOUT__ hwo = new HWAVEOUT__();
			HWAVEOUT__* hWOut = &hwo;
			IntPtr ptr2 = (IntPtr)(hWOut-1);(これで4つちいさくなる)
			waveOutOpen(ptr2, 0x0000, &wf, (uint)hWnd, 0, 0x00010000);

とすると,うまくいかない.

			HWAVEOUT__ hwo = new HWAVEOUT__();
			HWAVEOUT__* hWOut = &hwo;
			IntPtr ptr2 = (IntPtr)(&hWOut);
			IntPtr ptr3 = (IntPtr)(hWOut-1);
			waveOutOpen(ptr3, 0x0000, &wf, (uint)hWnd, 0, 0x00010000);

だと,うまくいく.&hWOutを扱う必要があるんだろうか…


試行錯誤の結果

		private HWAVEOUT__ hwo;
		private HWAVEOUT__* hWOut;
		private WAVEFORMATEX wf;
		private wavehdr_tag[] wt;
		private IntPtr ptr;
		private int k = 0;
		unsafe public void Open(IntPtr hWnd, uint SRATE)
		{
			//WAVEデバイス設定
			wf = new WAVEFORMATEX();
			wf.wFormatTag = 0x0001;     //PCM
			wf.nChannels = 1;           //モノラル
			wf.nSamplesPerSec = SRATE;  //周波数
			wf.wBitsPerSample = 16;     //16bit
			wf.nBlockAlign = (ushort)(wf.nChannels * wf.wBitsPerSample / 8);
			wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
			wf.cbSize = 0;              //PCMでは常に0
			hwo = new HWAVEOUT__();
			fixed (HWAVEOUT__* phwo = &hwo)
			{
				hWOut = phwo;

				fixed (HWAVEOUT__** pphwo = &hWOut)
				{
					ptr = (IntPtr)pphwo;
					fixed (WAVEFORMATEX* pwf = &wf)
					{
						waveOutOpen(ptr, 0x0000, pwf, (uint)hWnd, 0, 0x00010000);
					}
				}
			}
		}

で,できた.