Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Resolved Can't access C# member variables from a natively-invoked C# callback

Discussion in 'Scripting' started by sandalfoot, Jan 27, 2021.

  1. sandalfoot

    sandalfoot

    Joined:
    Jun 15, 2017
    Posts:
    3
    Hello all,

    I've got a bit of an interesting problem. I have a MonoBehavior that uses a native library (an Android .so). The library accepts a function pointer as a callback, and has a Process method that I run every Update(). If a certain condition is met, Process calls the callback.

    Here's the problem; even though the callback is a member of my MonoBehavior, and gets invoked properly (I can echo the passed-in data via Debug.Log), none of the MonoBehavior's member variables are accessible from within the callback; I get null reference exceptions, even if I attempt to check if the variable itself is null.

    Am I trying to do something that can't be done? I'm kind of new to this side of Unity and my experience with this native stuff is minimal.

    Here's some skeleton code to illustrate the issue;

    Code (CSharp):
    1.  
    2. public class SaturationMeterTest : MonoBehaviour
    3. {
    4.     // This value is assigned in the editor, and it is not null.
    5.     public SaturationMeter meter;
    6.  
    7.     // Start is called before the first frame update
    8.     void Start()
    9.     {
    10.         GetComponent<SaturationDetector>().setCallbacks(saturationNotificationCallback, saturationLoudnessCallback);
    11.     }
    12.  
    13.     public void saturationLoudnessCallback(IntPtr thisUserCtx, double loudnessDb)
    14.     {
    15.         // This line works
    16.         Debug.Log(string.Format("** Saturation Loudness: {0} **", loudnessDb));
    17.  
    18.        // This line throws a null exception
    19.        meter.DoSomethingWithData(loudnessDb);
    20.  
    21.        // So does this.
    22.        if (meter == null)
    23.               Debug.Log("Bad Juju");
    24.     }
    25. }
    26.  
    Code (CSharp):
    1.  
    2. public class SaturationDetector : MonoBehaviour
    3. {
    4.     [DllImport("libSaturationDetector")]
    5.     private static extern void SaturationDetector_SetCallbacks(IntPtr detector, IntPtr notificationCallback, IntPtr loudnessCallback, IntPtr thisUserCtx);
    6.  
    7. public void setCallbacks(NotificationCallbackDelegate notificationCallbackDelegate, LoudnessCallbackDelegate loudnessCallbackDelegate)
    8.     {
    9.         SaturationDetector_SetCallbacks(saturationDetector, Marshal.GetFunctionPointerForDelegate(notificationCallbackDelegate), Marshal.GetFunctionPointerForDelegate(loudnessCallbackDelegate), IntPtr.Zero);
    10.     }
    11. }
    12.  
    If I am trying to do something impossible, is there a workaround?
    Thanks in advance,
    Steve
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,315
    From the docs for
    GetFunctionPointerForDelegate
    :
    This StackOverflow for a similar issue seems to point to the same thing: https://stackoverflow.com/questions...ception-during-c-callback-to-c-sharp-function

    It appears as though you're losing the delegate to the GC, and all the scoped variables with it.
     
    Bunny83 and sandalfoot like this.
  3. sandalfoot

    sandalfoot

    Joined:
    Jun 15, 2017
    Posts:
    3
    It's a good theory, and I'll look into it next chance I get. What doesn't make sense, though, is that the delegate is a member of the MonoBehavior. Why would the GC collect it when the MonoBehavior still exists? It's attached to my app's Canvas and definitely hasn't been collected by the time the delegate is called. Is a delegate more than just a function pointer?
     
  4. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,051
    Yes, delegates are independent objects that are collected separately from the object they target. C# guarantees memory safety, so a delegate holds a reference to the target object, preventing it from being collected and the delegate from ever becoming invalid. But inversely, the target object doesn't hold any reference to the delegate, so the delegate can be collected while the target object still exists.
     
  5. sandalfoot

    sandalfoot

    Joined:
    Jun 15, 2017
    Posts:
    3
    Hi guys,

    Thanks for your help! I made a local variable to store the callbacks before passing them to the C++ layer and everything worked beautifully.

    Steve
     
    Madgvox likes this.