我正在尝试使用System.Media.SoundPlayer
API编写waveOut...
的替代品。当传递的文件/流完成播放时,此API会对我的SoundPlayer版本中的方法进行回调。
如果我创建一个表单范围的SoundPlayer实例并播放某些内容,一切正常,因为该表单使对象保持活动状态,因此委托处于活动状态以接收回调。
如果我在这样使用它,比如按钮点击事件:
SoundPlayer player = new SoundPlayer(@"C:\whatever.wav");
player.Play();
...它在99%的情况下工作正常,但偶尔(并且经常在文件很长的情况下)SoundPlayer对象在文件完成之前被垃圾收集,因此委托不再接收回调,我得到一个丑陋的错误。
我知道如何使用GCHandle.Alloc
“固定”对象,但只有当其他东西可以挂在句柄上时。对象是否有任何方法可以在内部固定自身,然后在一段时间(或完成播放)后取消固定?如果我尝试GCHandle.Alloc (this, GCHandleType.Pinned);
,我会得到一个运行时异常“对象包含非原始或非blittable数据。”
答案 0 :(得分:7)
您可以拥有所有“当前播放”声音的static
集合,只需在获得“完成播放”通知时移除SoundPlayer
实例即可。像这样:
class SoundPlayer
{
private static List<SoundPlayer> playing = new List<SoundPlayer>();
public void Play(...)
{
...
playing.Add(this);
}
// assuming this is your callback when playing has finished
public void OnPlayingFinished(...)
{
...
playing.Remove(this);
}
}
(显然需要锁定/多线程,错误检查等)
答案 1 :(得分:2)
您的SoundPlayer对象应该只存储在表单类的私有字段中,以便保持足够长的引用。您可能需要在表单关闭时处理它。
Fwiw,pinning不起作用,因为您的类缺少[StructLayout]属性。并不是说它可以有效地使用它,你必须将返回的GCHandle存储在某个地方,这样你以后可以取消它。您的表单类是存储它的唯一合理位置。简单。
答案 2 :(得分:1)
执行此操作的最佳方法是在私有静态字段中保留[ThreadStatic]
个活动SoundPlayer
列表,并在声音完成时从列表中删除每个实例。
例如:
[ThreadStatic]
static List<SoundPlayer> activePlayers;
public void Play() {
if(activePlayers == null) activePlayers = new List<SoundPlayer>();
activePlayers.Add(this);
//Start playing the sound
}
void OnSoundFinished() {
activePlayers.Remove(this);
}
答案 3 :(得分:1)
GCHandle是要走的路;只是不要指定Pinned枚举值。
类静态成员(例如shown here)的问题是,如果程序的被管部分不再引用它们,则可能过早收集对象 。引用的示例显示使用回调“OnPlayingFinished”,但现在您必须担心保持委托(引用OnPlayingFinished的委托)自己被垃圾收集。
您仍然需要注册OnPlayingFinished,并让代表保持活着状态。但是,GCHandle会保持您的对象存活,因此您可以使用以下代码保持代理:
class SoundPlayer
{
public void Play(...)
{
var h = GCHandle.Alloc(this);
SomeNativeAPI.Play(this, h.ToIntPtr());
}
// assuming this is your callback when playing has finished
delegate void FinishedCallback(IntPtr userData);
static FinishedCallback finishedCallback = OnPlayingFinished;
public static void OnPlayingFinished(IntPtr userData)
{
var h = GCHandle.FromIntPtr(userData);
SoundPlayer This = (SoundPlayer)h.Target;
h.Free();
... // use 'This' as your object
}
}
我们确保通过GCHandle可以访问我们的SoundPlayer。由于SoundPlayer的实例仍然可以访问,因此其静态成员也必须保持可访问状态。
至少,这是我最好的教育猜测,你可以如何去做。
答案 4 :(得分:0)
这可能听起来太简单了,但只是强烈引用您自己班级中的SoundPlayer对象。只要对象还活着,就应该让GC离开。
即。而不是:
public class YourProgram
{
void Play()
{
SoundPlayer player = new SoundPlayer(@"c:\whatever.wac");
player.Play();
}
}
此:
public class YourProgram
{
private SoundPlayer player;
void Play()
{
player = new SoundPlayer(@"c:\whatever.wac");
player.Play();
}
}
答案 5 :(得分:0)
您是否只是想阻止您的对象被垃圾收集?你不能打电话给GC.KeepAlive( this )
保护它免受GC的侵害吗?