|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionRecently, I was making a full-fledged blackjack card game, and wanted to play some sounds throughout the game. .NET 2.0 has made this extremely easy with the new The SoundPlayer problemThe problem with the Reproducing the problemDue to fact that the problem only appears when the the Garbage Collector runs at the instant just after the call to The code to reproduce the problemPlaying the soundSoundPlayer soundPlayer = new SoundPlayer();
soundPlayer.Stop();
soundPlayer.Stream = Properties.Resources.Windows_XP_Startup;
soundPlayer.Play();
Causing the garbage collector to runprivate void ProcessingThread()
{
// Do some work just so memory
// is being created and destroyed
while (processThread)
{
Thread.Sleep(10);
panel1.Invalidate();
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
TimeSpan ts = DateTime.Now.Subtract(dateTime);
int rotateAngle = (int)(ts.TotalMilliseconds / 100);
Math.DivRem(rotateAngle, 360, out rotateAngle);
Image image = null;
if (largeImage)
{
image = Properties.Resources.Bliss;
}
else
{
image = Properties.Resources.logo;
}
Matrix transformMatrix = new Matrix();
transformMatrix.RotateAt(rotateAngle,
new PointF(this.Width / 2, this.Height / 2));
e.Graphics.MultiplyTransform(transformMatrix);
e.Graphics.DrawImage(image, ClientRectangle);
transformMatrix.Dispose();
GC.Collect();
}
How to 100% reliably play embedded resource sounds asynchronouslyThe answer to this lies in the article here in Object Lifetime and Pinning. The using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace SoundPlayerBug
{
public static class SoundPlayerAsync
{
[DllImport("winmm.dll", SetLastError = true)]
public static extern bool PlaySound(byte[] ptrToSound,
System.UIntPtr hmod, uint fdwSound);
[DllImport("winmm.dll", SetLastError = true)]
public static extern bool PlaySound(IntPtr ptrToSound,
System.UIntPtr hmod, uint fdwSound);
static private GCHandle? gcHandle = null;
private static byte[] bytesToPlay = null;
private static byte[] BytesToPlay
{
get { return bytesToPlay; }
set
{
FreeHandle();
bytesToPlay = value;
}
}
public static void PlaySound(System.IO.Stream stream)
{
PlaySound(stream, SoundFlags.SND_MEMORY |
SoundFlags.SND_ASYNC);
}
public static void PlaySound(System.IO.Stream stream,
SoundFlags flags)
{
LoadStream(stream);
flags |= SoundFlags.SND_ASYNC;
flags |= SoundFlags.SND_MEMORY;
if (BytesToPlay != null)
{
gcHandle = GCHandle.Alloc(BytesToPlay,
GCHandleType.Pinned);
PlaySound(gcHandle.Value.AddrOfPinnedObject(),
(UIntPtr)0, (uint)flags);
}
else
{
PlaySound((byte[])null, (UIntPtr)0, (uint)flags);
}
}
private static void LoadStream(System.IO.Stream stream)
{
if (stream != null)
{
byte[] bytesToPlay = new byte[stream.Length];
stream.Read(bytesToPlay, 0, (int)stream.Length);
BytesToPlay = bytesToPlay;
}
else
{
BytesToPlay = null;
}
}
private static void FreeHandle()
{
if (gcHandle != null)
{
PlaySound((byte[])null, (UIntPtr)0, (uint)0);
gcHandle.Value.Free();
gcHandle = null;
}
}
}
[Flags]
public enum SoundFlags : int
{
SND_SYNC = 0x0000, // play synchronously (default)
SND_ASYNC = 0x0001, // play asynchronously
SND_NODEFAULT = 0x0002, // silence (!default) if sound not found
SND_MEMORY = 0x0004, // pszSound points to a memory file
SND_LOOP = 0x0008, // loop the sound until next sndPlaySound
SND_NOSTOP = 0x0010, // don't stop any currently playing sound
SND_NOWAIT = 0x00002000, // don't wait if the driver is busy
SND_ALIAS = 0x00010000, // name is a registry alias
SND_ALIAS_ID = 0x00110000, // alias is a predefined id
SND_FILENAME = 0x00020000, // name is file name
}
}
The important part of the above code is: gcHandle = GCHandle.Alloc(BytesToPlay, GCHandleType.Pinned);
PlaySound(gcHandle.Value.AddrOfPinnedObject(), (UIntPtr)0, (uint)flags);
The byte array that is about to be played is pinned so the garbage collector can not move or collect it. Unpinning the byte array is also very important, otherwise you will have a memory leak. if (gcHandle != null)
{
PlaySound((byte[])null, (UIntPtr)0, (uint)0);
gcHandle.Value.Free();
gcHandle = null;
}
Why would Microsoft resolve this as "By Design"I believe Microsoft has resolved my bug report as "By Design" as there are Garbage Collector performance implications when objects are manually pinned. This is of increased concern with the Ways to avoid the problem
Thank youCodeProject has helped me in many ways over the years, so I hope this article saves some people the headache I had in trying to locate and resolve the above problem. This is also my first attempt at an article, so any feedback would be greatly appreciated.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||