Native (C++) library crash on Android on use of callback to C# managed function. (SIGSEGV Code = 2)

Hello,

I have spent most of my day hitting my head against this problem, would love if someone could sanity check it for me. Pretty please!

I have a native C++ library, which is multi-platform, but for Android compiles to a .so. Within this library I have a Logger utility that stores a function pointer to a managed C# delegate function. The logger utility receives any logged messages from the library during the runtime, and passes them back to Unity using a delegate function, and then logged using the Debug.* functions in Unity / C#.

This setup has worked just fine for a number of months, but something has changed somewhere, either in Unity or my own code, that now makes it results in the attached logcat output. I have the same problem on an S8 (Running 7.1) and a Pixel (running 8.0) , which both have used earlier versions of the same libraries with no issues. Notably this works fine on both Windows and iOS.

Note this is on Unity 2017.3.0f2, I’m not able to easily step back and check the current codebase on an older version as I am working against ARCore Preview 2. I have tried both Mono and IL2CPP and both crash in the same place.

On the C# side the relevant code looks like this…

Native / managed interface:

// callback functionality
public delegate void DebugCallback(string message, MsgType type);

// addition to double check problems were not because of garbage collection of the delegate instance
public static DebugCallback debugCallback;

internal static class ARLibInterop
{
    [DllImport(ARLibInterface.pluginName, CallingConvention = CallingConvention.Cdecl)]
    internal static extern void InitialiseARSession(ARSessionConfig sessionConfig, DebugCallback debugCallback);
}

//...

public void Initialise()
{
     debugCallback = new DebugCallback(DebugMethod);
     debugCallback("testing before call", MsgType.LOG);
     ARLibInterop.InitialiseARSession(config, debugCallback);
}

#if UNITY_IOS
[MonoPInvokeCallback(typeof(DebugCallback))]
#endif
public static void DebugMethod(string message, MsgType type)
{
    // do stuff
}

MsgType is a simple enum:

[Serializable]
    public enum MsgType
    {
        NONE = 0,
        ERROR = 1,
        WARN = 2,
        LOG = 3
    }

On the C++ side

enum MessageType
{
    MSG_NONE = 0,
    MSG_ERROR = 1,
    MSG_WARN = 2,
    MSG_LOG = 3
};

typedef void(__stdcall * DebugCallback) (const char * str, MessageType type);

extern "C" void InitialiseARSession(ARSessionConfig sessionConfig, DebugCallback debugCallback)
{
#if PLATFORM == PLATFORM_ANDROID
    __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "callback received by native interface is %p", debugCallback);
#endif
    debugCallback("test", MSG_LOG);
    //ARLib::Initialise(sessionConfig, debugCallback);
}

Going slightly insane as the function is valid before the native call, but crashes native side. This utility has been functioning fine on all platforms for months, so cannot think what is causing this now. What am I missing?!

3341830–260953–log.txt (1.73 KB)

First of all I’d use this to create a pointer to function and pass it to native side as IntPtr (C# side, native is good as it is):

If that doesn’t help, try removing the arguments from that function and add them back by one to find the abusing one. Perhaps some marshaling is required.

Thank you! I hadn’t come across Marshal.GetFunctionPointerForDelegate, will work that in. All the examples I had seen used the above so I have just stuck with that.

I figured out the problem about ten minutes after posting, though only just had the chance to check it was definitely it as I wasn’t at my laptop. Like you suggested I started stripping out arguments which made me twig on the way home while running through my GitHub changelog for the hundredth time today.

Native:

    struct ARSessionConfig
    {
        MessageType verbosityLevel;
        bool flip;

        friend std::ostream& operator<<(std::ostream& os, const ARSessionConfig& config);
    };

Managed:

    [StructLayout(LayoutKind.Sequential), Serializable]
    public struct ARSessionConfig
    {
        public MsgType loggerVerbosity;
        //public bool flip;
    }

An entire day, caused by a stray bool, which presumably threw the offset when hooking up the parameters native side in Android, where Windows still dealt with it.