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

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.

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?

1 Like

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 ?

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;
}

}

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.

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

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

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

So the problem is that this doesn’t always work the way you’d expect. In the example the event instance is managed, but when you are borrowing the instance from e.g. a studio event emitter it might be destroyed before you can set its userData to IntPtr.Zero (but still inexplicably trigger a callback, which was happening in my case).

May I suggest the example be updated with the following instead?

        UserData userData;
        try
        {
            var instance = new EventInstance(instancePtr);
            instance.getUserData(out var userDataPtr);
            var userDataHandle = GCHandle.FromIntPtr(userDataPtr);
            userData = (UserData)userDataHandle.Target;
        }
        catch (Exception e)
        {
            // ArgumentException catches "GCHandle value belongs to a different domain"
            // InvalidOperationException catches "GCHandle value cannot be Zero"
            // The later is thrown when user data is not set: catch it if you don't want a simple mistake to force an editor freeze.
            if (e is ArgumentException || e is InvalidOperationException)
            {
                return RESULT.OK;
            }

            throw e;
        }

The try catch here is much safer in my opinion, as you’re not avoiding the symptom but providing the cure.

New tactic: since this threw another exception, and since it’s really annoying that these exceptions keep freezing the editor or play music continuously I opted for something even better instead:

        try
        {
            // same as above
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return RESULT.OK;
        }

This is the real fix: no blocking of editor, nice workflow, clear error messaging: beautiful.

Hi,

Thank you for looking into this we really appreciate it. I will pass on your suggestions to our development team to look into further.

1 Like