IL2CPP Crash on GCHandle.Alloc for callbacks

IL2CPP Crash on GCHandle.Alloc for callbacks

Unity: 2021.1.23f1
FMOD: 2.02.03
Scripting Backend: IL2CPP

Works great in editor.
Crashed on compiled.
Works great with Mono

ArgumentException: Object contains non-primitive or non-blittable data.

Example code.

FMOD.Studio.EventInstance eventInstance = FMODUnity.RuntimeManager.CreateInstance("event:/example");

TimelineInfo timelineInfo = new TimelineInfo();

// this will crash the IL2CPP compiler
GCHandle timelineHandle = GCHandle.Alloc(timelineInfo, GCHandleType.Pinned); 
eventInstance.setUserData(GCHandle.ToIntPtr(timelineHandle));

FMOD.Studio.EVENT_CALLBACK beatCallback = new FMOD.Studio.EVENT_CALLBACK(EventCallback);
eventInstance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER);

FMOD.RESULT EventCallback(
		FMOD.Studio.EVENT_CALLBACK_TYPE type, 
		IntPtr instancePtr, 
		IntPtr parameterPtr
){
//  do work on event here..
return FMOD.RESULT.OK;
}

Is there an alternative way to get timeline callbacks without GCHandle.Alloc ?

It looks like you are using the 2.00 timeline example with the 2.02 integration- can you try using the 2.02 timeline example instead? One change is that we no longer recommend pinning the memory with GCHandleType.Pinned because it was causing some issues on iOS, but we haven’t encountered this with IL2CPP.

Can confirm 2.02 timeline example doesn’t crash on IL2CPP for various platforms.
seeing some marshal errors but might be unrelated

To marshal a managed method, please add an attribute named ‘MonoPInvokeCallback’ to the method definition.

Thanks for confirming- does your callback method (EventCallback from your above snippet) have this attribute added above it?

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]

looks like this

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
    static FMOD.RESULT BeatEventCallback2(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr){

Calling a singelton on end that crash the program

case FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED:
					{
						Debug.Log("Song ended event called");
						RythmManager.instance.OnSongEnd();
					}
				break;

Errors like

WindowsPlayer UnityException: IsObjectMonoBehaviour can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityException: get_isActiveAndEnabled can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

What is the best way to get back to the main thread from a

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]

Hmm, there are probably some sophisticated .NET ways of dealing with this, but the simplest way I can think of would be to call RythmManager.instance.OnSongEnd() from Update(), and use an atomic operation to trigger it from your callback. Something like:

case FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED:
    {
        Debug.Log("Song ended event called");
        onSongEndCalled = true; // Atomic primitive type assignment
    }
    break;

And then in Update()

if(onSongEndCalled)
{
    RythmManager.instance.OnSongEnd();
    onSongEndCalled = false;
}

Should work around that error.