How to correctly set user data for DSP and system callbacks using Core API and C#?

Hi,

I’m trying to solve a bug in my Unity application that I think is caused by using GC handles incorrectly to pin managed (userData) objects. I’m using the Core API directly and might be doing something wrong, but the available documentation differs about some core details.

I found two Unity C# examples on how to set user data in the documentation:

In the timeline example, the GCHandle is pinned and the TimelineInfo class has the [StructLayout(LayoutKind.Sequential)] attribute set, while the DSP capture example has neither. It seems to me that the handle should also be pinned in the DSP capture example, since otherwise the object could be moved by the GC without the unmanaged pointer being updated.

I’m also not entirely clear on what a userdata object should contain. Currently I’m just using it to store a reference to a class instance, and then calling a function on that instance from the static callback, like so:

    [StructLayout(LayoutKind.Sequential)]
    private class DSPUserData
    {
        public SomeClass Instance { get; }

        public DSPUserData(SomeClass instance)
        {
            Instance = instance;
        }
    }

    [AOT.MonoPInvokeCallback(typeof(FMOD.DSP_READCALLBACK))]
    private static FMOD.RESULT ReadCallback(ref FMOD.DSP_STATE dspState, IntPtr inBuffer, IntPtr outBuffer, uint length, int inChannels, ref int outchannels)
    {
        FMOD.DSP_STATE_FUNCTIONS functions = (FMOD.DSP_STATE_FUNCTIONS)Marshal.PtrToStructure(dspState.functions, typeof(FMOD.DSP_STATE_FUNCTIONS));

        IntPtr userDataPtr;
        FMOD.RESULT result = functions.getuserdata(ref dspState, out userDataPtr);

        if (result == FMOD.RESULT.OK && userDataPtr != IntPtr.Zero)
        {
            GCHandle gcHandle = GCHandle.FromIntPtr(userDataPtr);
            DSPUserData userData = (DSPUserData)gcHandle.Target;

            userData.Instance.SomeFunction();
        }

        return FMOD.RESULT.ERR_DSP_SILENCE;
    }

So my specific questions are:

  1. Should managed objects set as userdata for a system or DSP callback always be pinned and be blittable?
  2. Can the managed userdata object contain a reference to an instance of an object that is then used to call a function in the callback?
  3. If I’m not targeting IL2CPP, can I safely set a class instance function as a callback instead of a static function?

Pinning should only be required when the native code is actually touching/modifying the data being passed in, and userdata is only passed through the native FMOD code to the callback.

The ‘Timeline Callbacks’ example should probably be updated to have to the pinning and structLayout removed.

The userdata can be anything, you can customize it to pass through whatever information you may need.

Going by Unity - Manual: Scripting restrictions:

Managed methods that need to be marshaled to a C function pointer so that they can be called from native code have a few restrictions on AOT platforms:

  • The managed method must be a static method
  • The managed method must have the [MonoPInvokeCallback] attribute

What is the exact bug you are referring to?

Thanks for answering my questions.

What is the exact bug you are referring to?

I’ve never been able to reproduce the bug locally, but several users have reported it. After about 45 ~ 60 minutes of using the application, all audio playback stops, and also some of the VR input controls (ex: buttons, grabbing objects, etc…) seem to randomly stop working.

The fact that it only happens after a certain period and seems to affect the app in unpredictable and random ways, makes me suspect it’s a memory corruption issue. One of my theories was that it might be caused by the unmanaged FMOD code using an outdated pointer to a managed object (ex: callback, userData) that had been moved by the GC.

Do you have access to any of the crash dumps, logs or reports at all?

I don’t have any crash dumps, as the application itself actually does not crash in the instances that have been reported by users, but I have received some crash reports related to FMOD through cloud diagnostics which might be related.

Here’s the most recent example (changed the extension from .json to .txt so I could upload it): NvExg3gBVouaglDsmPUy_data.txt (351.9 KB)

Problem

Native Crash - Unknown Function (fmodstudio)

Relevant thread:

 Thread 53 (crashed)
0   ntdll                              0x00007ffa250ed764 ZwWaitForMultipleObjects 
1   KERNELBASE                         0x00007ffa227e0d40 WaitForMultipleObjectsEx 
2   KERNELBASE                         0x00007ffa227e0c3e WaitForMultipleObjects 
3   UnityPlayer                        0x00007ff986579a5d ?HandleCrash@CrashHandlerInternal@winutils@@QEAAXKKPEBDPEAU_CONTEXT@@PEAU_EXCEPTION_RECORD@@_N@Z 
4   UnityPlayer                        0x00007ff986579b0e ?HandleCrash@ExternalCrashHandler@winutils@@YAXKKPEAU_EXCEPTION_POINTERS@@@Z 
5   UnityPlayer                        0x00007ff986588f38 ?ProcessInternalCrash@winutils@@YAHPEAU_EXCEPTION_POINTERS@@_N@Z 
6   KERNELBASE                         0x00007ffa2289c247 UnhandledExceptionFilter 
7   ntdll                              0x00007ffa250f5070 RtlUserThreadStart$filt$0 
8   ntdll                              0x00007ffa250dc776 _C_specific_handler 
9   ntdll                              0x00007ffa250f1f6f RtlpExecuteHandlerForException 
10  ntdll                              0x00007ffa250a1454 RtlDispatchException 
11  ntdll                              0x00007ffa250f0a9e KiUserExceptionDispatch 
12  fmodstudio                         0x00007ff985df2bf0 <system symbols missing> 
13  fmodstudio                         0x00007ff985dff05f <system symbols missing> 
14  fmodstudio                         0x00007ff985e07481 <system symbols missing> 
15  fmodstudio                         0x00007ff985e054ac <system symbols missing> 
16  fmodstudio                         0x00007ff985e27724 <system symbols missing> 
17  fmodstudio                         0x00007ff985e274a4 <system symbols missing> 
18  fmodstudio                         0x00007ff985e27299 <system symbols missing> 
19  fmodstudio                         0x00007ff985e2ffc9 <system symbols missing> 
20  fmodstudio                         0x00007ff985e240a1 <system symbols missing> 
21  fmodstudio                         0x00007ff985e2c8c6 <system symbols missing> 
22  fmodstudio                         0x00007ff985e9ed61 <system symbols missing> 
23  KERNEL32                           0x00007ffa23877034 BaseThreadInitThunk 
24  ntdll                              0x00007ffa250a2651 RtlUserThreadStart 
25  ntdll                              0x00007ffa250a2651 RtlUserThreadStart

One thing that I noticed I did differently than in the DSP capture example was how I called the getUserData function in the DSP’s read callback.
In the previous version of my app (which the report I posted above is from) I used this code:

[AOT.MonoPInvokeCallback(typeof(FMOD.DSP_READCALLBACK))]
private static FMOD.RESULT ReadCallback(ref FMOD.DSP_STATE dspState, IntPtr inBuffer, IntPtr outBuffer, uint length, int inChannels, ref int outchannels)
{
	FMOD.DSP dsp = new FMOD.DSP();
	dsp.handle = dspState.instance;

	IntPtr userDataPtr;
	FMOD.RESULT result = dsp.getUserData(out userDataPtr);
		
	.......
}

In the latest version I’ve now changed it to match the DSP capture example:

FMOD.DSP_STATE_FUNCTIONS functions = (FMOD.DSP_STATE_FUNCTIONS)Marshal.PtrToStructure(dspState.functions, typeof(FMOD.DSP_STATE_FUNCTIONS));
FMOD.RESULT result = functions.getuserdata(ref dspState, out userDataPtr);

Could my previous way of calling the getUserData function cause these type of issues?

Both of these methods should work and do the same thing. Although it isn’t recommended to use the first method as you should not be using the public API in a plugin, plugins are usually meant to be a standalone and therefore cannot reference the public API, only the plugin API, but in this case it should be fine to do.

As for the logs, unfortunately there are no symbols so we cannot begin to speculate at the cause. Are you able to try reproducing the issue while using the FMOD logging libs, this will provide much more information.

Hi cameron,

Thanks again for your detailed answers, it really helped me clear up some of the misunderstandings I had.

I’m not able to reproduce the issue locally, but Unity cloud diagnostics allows me to upload symbol files for crash reporting.

Are there .pdb files available for the fmodstudio dlls included in the Unity integration?

I can make the pdb’s available to you, what is the exact version of FMOD you are using?

I’m using the dlls included in the Unity integration from version 2.01.07

I have uploaded the zip containing pdb’s to your FMOD Profile.

Thanks, I’ve successfully uploaded them to Unity’s cloud diagnostics.

I’ll get back in touch if another FMOD related crash is reported.

1 Like