GCHandle value belongs to a different domain- Error in Unity playmode - music continues to play


(Patric Corletto) #1

I’m using the example script in the tutorials for unity called ScriptUsageTimeline.

I’m getting this error when I stop play mode:

ArgumentException: GCHandle value belongs to a different domain
System.Runtime.InteropServices.GCHandle.op_Explicit (IntPtr value) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.InteropServices/GCHandle.cs:127)
System.Runtime.InteropServices.GCHandle.FromIntPtr (IntPtr value) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.InteropServices/GCHandle.cs:172)
ScriptUsageTimeline.BeatEventCallback (EVENT_CALLBACK_TYPE type, EventInstance instance, IntPtr parameterPtr) (at Assets/_scripts/utility/ScriptUsageTimeline.cs:96)
(wrapper native-to-managed) ScriptUsageTimeline:BeatEventCallback (FMOD.Studio.EVENT_CALLBACK_TYPE,FMOD.Studio.EventInstance,intptr)
FMOD.Studio.System.release () (at Assets/thirdParty/Fmod/Plugins/FMOD/Wrapper/fmod_studio.cs:371)
FMODUnity.RuntimeManager.OnDestroy () (at Assets/thirdParty/Fmod/Plugins/FMOD/RuntimeManager.cs:470)

The music will continue playing until i force quit unity manually. Any hints how to solve this ?

Thank you.


(Cameron Baron) #2

This error usually happens when an attempt to get a GCHandle from an IntPtr after that GCHandle has been freed.

What version of FMOD and Unity are you using?
Is the file edited in any way?


(Patric Corletto) #3

Thank you Cameron.
Unity is on 2018.1.1f1 (64bit)
and FMOD 1.10.05

The file is edited but just invoking a callback and I’m passing the emitter EventInstance -> I assume the error comes from there. Any tipp how to avoid this ?


(Patric Corletto) #4

using System;
using System.Runtime.InteropServices;
using UnityEngine;

class ScriptUsageTimeline : MonoBehaviour
{
// Variables that are modified in the callback need to be part of a seperate class.
// This class needs to be ‘blittable’ otherwise it can’t be pinned in memory.
[StructLayout(LayoutKind.Sequential)]
class TimelineInfo
{
public int currentMusicBar = 0;
public int currentMusicBeat = 0;

    public FMOD.StringWrapper lastMarker = new FMOD.StringWrapper();
}

TimelineInfo timelineInfo;
GCHandle timelineHandle;

FMOD.Studio.EVENT_CALLBACK beatCallback;
FMOD.Studio.EventInstance musicInstance;

public FMODUnity.StudioEventEmitter battleMusicEmitter;
static Battle battleScript;


void Start()
{
    timelineInfo = new TimelineInfo();

    battleScript = GetComponent<Battle>();
    // Explicitly create the delegate object and assign it to a member so it doesn't get freed
    // by the garbage collected while it's being used
    beatCallback = new FMOD.Studio.EVENT_CALLBACK(BeatEventCallback);

    //musicInstance = FMODUnity.RuntimeManager.CreateInstance("event:/music/battles/hati_skol_battlemusic");

    musicInstance = battleMusicEmitter.EventInstance;
    // Pin the class that will store the data modified during the callback
    timelineHandle = GCHandle.Alloc(timelineInfo, GCHandleType.Pinned);
    // Pass the object through the userdata of the instance
    musicInstance.setUserData(GCHandle.ToIntPtr(timelineHandle));
    
    musicInstance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT | FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER);
    //musicInstance.start();
}

void OnDestroy()
{
    musicInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
    musicInstance.release();
    timelineHandle.Free();
}

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT BeatEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, FMOD.Studio.EventInstance instance, IntPtr parameterPtr)
{
    // Retrieve the user data
    IntPtr timelineInfoPtr;
    instance.getUserData(out timelineInfoPtr);

    // Get the object to store beat and marker details
    GCHandle timelineHandle = GCHandle.FromIntPtr(timelineInfoPtr);
    TimelineInfo timelineInfo = (TimelineInfo)timelineHandle.Target;

    switch (type)
    {
        case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT:
            {
                var parameter = (FMOD.Studio.TIMELINE_BEAT_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_BEAT_PROPERTIES));
                if(parameter.bar != timelineInfo.currentMusicBar)
                    battleScript.TriggerPhase();
                battleScript.BeatTest();

                timelineInfo.currentMusicBar = parameter.bar;
                timelineInfo.currentMusicBeat = parameter.beat;
                //Debug.Log(String.Format("Current Bar = {0}, Last Marker = {1}", timelineInfo.currentMusicBar, (string)timelineInfo.lastMarker));
                
            }
            break;
        case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER:
            {
                var parameter = (FMOD.Studio.TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_MARKER_PROPERTIES));
                timelineInfo.lastMarker = parameter.name;
                Debug.Log(timelineInfo.lastMarker);
            }
            break;
    }
    return FMOD.RESULT.OK;
}

}


(Cameron Baron) #5

Thanks for your patience, it looks like there is a chance that callback will be called one last time as the FMOD system releases but after the GCHandle has already been freed.

By overwriting the musicInstances user data in the OnDestroy, we can avoid this.

void OnDestroy()
{
    musicInstance.setUserData(IntPtr.Zero);
    ...

I will make sure this is added to the next release, coming at the end of the month.


(Tanner) #6

I get a “user data cannot be set to IntPtr.Zero” error when I do this


(Cameron Baron) #7

After getting the UserData you will want to only proceed if it is not zero.

if (timelineInfoPtr != IntPtr.Zero)
{
    GCHandle timelineHandle = GCHandle.FromIntPtr(timelineInfoPtr);
    ...