回调委托被收集了吗?

18

我一直在尝试使用FMOD进行C#游戏开发,但早期遇到了一个问题,似乎无法解决。我想要做一些分支音频处理并将一些游戏动作与节拍同步,因此尝试将同步点添加到我的音乐轨道上。以下是代码:

public class Music
{
    private Sound music;
    private Channel channel;
    private IntPtr syncPtr;

    public string File { get; private set; }  

    public Music(string file)
    {
        File = file;
    }

    public void Load()
    {
        music = new Sound();
        Audio.System.createSound(File, MODE.HARDWARE, ref music);
    }

    public void Unload()
    {
        music.release();
    }

    public virtual void Play()
    {
        Audio.System.playSound(channel == null ? CHANNELINDEX.FREE : CHANNELINDEX.REUSE, music, false, ref channel);
        music.addSyncPoint(500, TIMEUNIT.MS, "wooo", ref syncPtr);
        channel.setCallback(channelCallback);
    }

    private RESULT channelCallback(IntPtr channelraw, CHANNEL_CALLBACKTYPE type, IntPtr commanddata1, IntPtr commanddata2)
    {
        if (type == CHANNEL_CALLBACKTYPE.SYNCPOINT)
            Console.WriteLine("sync!");

        return RESULT.OK;
    }
}

然后...

m = new Music(MUS_TUTORIAL);  //m is static
m.Load();
m.Play();
歌曲可以正常加载和播放,直到达到我添加的500毫秒同步点。此时,VC#会从FMOD.EventSystem.update()中输出以下错误:
“在回收的委托类型'Game!FMOD.CHANNEL_CALLBACK :: Invoke'上进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。当将委托传递给非托管代码时,必须由托管应用程序保持其生存状态,直到确保它们永远不会被调用。”
所以,某种方式FMOD正在失去我传递给它的委托的跟踪。持有该委托的Music实例尚未被垃圾回收——我现在将其存储在静态变量中——但我也尝试过使用静态方法来解决问题,但无法成功。如果禁用CallbackOnCollectedDelegate MDA,则错误变为空引用异常,因此MDA没有错误。我想我可能只是没有完全理解FMOD在这里的工作方式。
是否有任何C#+FMOD专家能够看出我的错误?

1
请注意,您不需要在标题中添加“C# - FMOD -”等内容。标签可以实现相同的功能,而不会使您的标题变得丑陋。 - John Saunders
2个回答

35
    channel.setCallback(channelCallback);

这就是问题陈述。FMod是非托管代码。您在此处创建委托对象并将其传递给非托管代码。问题在于,垃圾回收器无法跟踪本机代码持有的引用。下一次垃圾回收将找不到对该对象的任何引用并将其收集起来。当本机代码进行回调时,系统就会出现问题。

为了避免这种情况发生,您需要自己保留一个引用:

public class Music
{
    private SomeDelegateType callback
    //...
    public Music(string file)
    {
        File = file;
        callback = new SomeDelegateType(channelCallback);
    }

    public virtual void Play()
    {
        Audio.System.playSound(channel == null ? CHANNELINDEX.FREE : CHANNELINDEX.REUSE, music, false, ref channel);
        music.addSyncPoint(500, TIMEUNIT.MS, "wooo", ref syncPtr);
        channel.setCallback(callback);
    }

你需要从FMod封装代码中找到实际的委托类型,我只是猜测了“SomeDelegateType”。


啊,好的,谢谢。那么这是我对委托工作方式的误解。 - Matt
4
代理是一个独立的对象。自C# 2.0以来支持的快捷语法有时会隐藏这一点,编译器会代表您进行转换。在您的情况下,代码channel.setCallback(channelCallback);被编译器转换为channel.setCallback(new SomeDelegateType(channelCallback)); - Bevan
2
顺便夸一下,你的问题描述得非常好,让我很容易地帮助你。问题描述完美,代码也贴得非常准确。加1。 - Hans Passant

0
我曾在VB.NET与自定义C++ DLL之间遇到了类似的问题。感谢@Hans的帮助,问题已得到解决。这个网站已经帮我解决了很多问题,我为此感激不尽。我将我的问题和解决方案分享出来,希望能够帮到其他在不同上下文中遇到相同问题的人。
在模块中声明如下内容:
Public Delegate Sub CB_FUNC(ByVal x As Integer, ByVal y As Integer)
Public Declare Sub vidProc_cb_MouseClick Lib "C:\Users\.....\vidProc\product\vidProc.dll" (ByVal addr_update As CB_FUNC)

原文:

在button_click子程序中有一个简单的调用:

vidProc_cb_MouseClick(AddressOf updateXY)

我曾经遇到过“CallbackOnCollectedDelegate”错误。不是立即出现的,而是在与表单上的其他对象交互后,尝试调用回调函数时(在我的情况下是在OpenCV窗口中进行鼠标单击)。

解决方法:

1)在表单类声明中声明

Private addr_update As CB_FUNC

2) 在表单加载时定义addr_update

addr_update = New CB_FUNC(AddressOf updateXY)

3) 在 button_click 子程序中使用新指针调用我的“设置回调”函数

vidProc_cb_MouseClick(addr_update)

我想我理解了 @Hans 的意思并正确地实现了它(我无法复制错误)。希望这能帮助到某些人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接