Passing callback to separate thread spawned from native C plugin

So I have the following scenario:
I have written a simple C plugin which creates a thread:

void CreateGameLoopThread()
{
#if WINDOWS_BUILD
    int thread_id;
    DWORD dd;
    threadHandle = _beginthread(myThreadFun, 0, &thread_id);
    /*
    threadHandle = CreateThread(
                NULL,                   // default security attributes
                0,                      // use default stack size 
                myThreadFun,       // thread function name
                NULL,          // argument to thread function
                0,                      // use default creation flags
                dd);   // returns the thread identifier
                */
#else
    int x = 0;
    pthread_t inc_x_thread;
    pthread_create(&inc_x_thread, NULL, inc_x, &x);
#endif
}

That thread is invoking a callback onto the C# unity side and I want to use that callback to do various stuff like enabling disabling UI stuff changing variables etc etc. Now I know that the unity api can only be called from the main thread. So what I am doing is on the callback I do something like this :

        void SetBattleUI(bool isSkillRound)
        {
            UnityThread.executeInUpdate(SetUI);
        }

Which seems to be invoking the Unity API with success and without any errors. My problem is that the variables inside SetUI are zero’d out. So let’s say if in SetUI i have an int variable inside the monobehaviour it will have set that int to 0 (and if it’s a reference type it will be null). One way to solve this problem is by setting these variables as “static volatile” but that seems a bit weird to me because those variables should be stored on the heap anyway and it doesn’t make sense that I have to do that.
Maybe it’s just the way I should go about it?

More code just to illustrate the problem:

    class BattleSystem : MonoBehaviour
    {
        public volatile int p = 3; //Get's 0'd out
        public static volatile int staticP = 3; //Works

        void SetBattleUI(bool isSkillRound)
        {
            UnityThread.executeInUpdate(SetBUI);
        }

        void SetBUI()
        {
            Debug.Log(p); //0
            Debug.Log(staticP); //3
        }

I’ve never called from unmanaged to managed, only polled the other way around to retrieve things to do back to the C# side, usually in the form of a JSON blob that I can action upon in C#.

But is this perhaps part of your issue?

Until recently I thought the only option was UnitySendMessage() but apparently more is possible now.

Hello thanks for your input, but I believe that my problem is different. Let me add some more code to show perhaps better what I am doing. Also note that I am using pure C and not objective C for iOS etc.

typedef void ( __stdcall *ChangeHealth )(uint8_t*, int , int); //Buffer* , bufferSize , PlayerType
typedef void ( __stdcall *SetRoundUI )(int); //IsSkillRound
static SetRoundUI setRoundCallback;
static ChangeHealth changeHealthFunc;

//I call this to set the callbacks
EXPORT void SetCallbackFunctions(ChangeHealth unityChangeHealthCallback , SetRoundUI setRoundUI)
{
    Log("Set callback functions");
    changeHealthFunc = unityChangeHealthCallback;
    setRoundCallback = setRoundUI;
}

//Thread function that uses the callback (on a different thread)
void myThreadFun(void *vargp)
{
    while (1)
    {
        Sleep(SERVER_TICK_RATE); //Server tick rate(atm is 1 second)
        EnterCriticalSection(&CriticalSection);
        //potionList
        HP--;
        Log("Time ticking: %d" , HP);
        if(HP < 8 )
        {
            Log("HP LESS THAN 8");
            setRoundCallback(TRUE); //callback back to Unity
        }
        gll_each_with_position(potionList , TickPotion);
        if(killApp)
        {
            Log("KILL THE THREAD FROM LOCAL");
            _endthread();
        }
        LeaveCriticalSection(&CriticalSection);
    }
}

What does the C# side look like where it calls SetCallbackFunctions()? It would seem to me that the simplicity of cdecl-style callback is really not gonna let you call anything but a static target.

Remember, instance method calls are actually internally something like this (as written in pure C):

void MyFunc( void *This, int arg1);

Something has to pass the “this” context around.

But I don’t think you can get at any object instance stuff from C side. You need some kind of wrapper “lander” function on the C# side and I would intuit that it would need to be static.

I am not sure I quite understood what you said about the “*This” is that what’s implicitly happening?

Some more code:

Here is my awake call:

       void Awake()
        {
            s_SkillRoundContainer = m_SkillRoundContainer;
            s_NormalRoundContainer = m_NormalRoundContainer;
            m_DebugMenu.PassDataForDebugMenu(p1Stats);
            UnityThread.initUnityThread();
            bnd = new BattleNativeDelegates();
            InitDelegate Init = bnd.InitDelegate;

            InitCallbacks initCallbacks = bnd.InitCallbacks;
            initCallbacks.Invoke(ChangeStatsBy , SetBattleUI);

            byte[] bytesToPass = p1Stats.SerializeStatsToBytes();
            Init.Invoke(p1Stats.SerializeStatsToBytes(), p2Stats.SerializeStatsToBytes(), bytesToPass.Length);
        }

And here is the lib loading and function binding

   //TODO(JohnMir): Move this to a seperate file and comment what these delegates do
    //Add one delegate for each function
    //LIBRARY_0
    public delegate void ChangeStatsBy(IntPtr sp, int size, int playerChangingHP);
    public delegate int TwoIntsDelegate(int n, int m);
    public delegate int myCallbackDelegate(TwoIntsDelegate dl, int n, int m);
    public delegate void SetRoundUI(bool isSkillRound);
    public delegate void AddPotDelegate(int potType);

    public delegate void InitDelegate(byte[] p1Stats, byte[] byte2, int pvp);
    public delegate void InitCallbacks(ChangeStatsBy changeStatsDelegate , SetRoundUI setRoundUI);
    public delegate void Cleanup();

    //Changing HP and Faith Delegates

    public class BattleNativeDelegates
    {
        //Lib 0
        const string LIBRARY_PATH_0 = "/BattleSystem/windows_plugin/NativeCode.dll";
        public myCallbackDelegate MyCallbackDelegate => GetDelegate<myCallbackDelegate>(m_LibraryHandles[0], "TakesCallback");
        public AddPotDelegate AddPot => GetDelegate<AddPotDelegate>(m_LibraryHandles[0], "AddPot");

        //Init functions
        public InitDelegate InitDelegate=> GetDelegate<InitDelegate>(m_LibraryHandles[0] , "Init");
        public InitCallbacks InitCallbacks => GetDelegate<InitCallbacks>(m_LibraryHandles[0], "SetCallbackFunctions");
        public Cleanup Cleanup => GetDelegate<Cleanup>(m_LibraryHandles[0], "Cleanup");


        //Other vars
        public List<IntPtr> m_LibraryHandles = new List<IntPtr>();

        #region DllLoading
        [DllImport("kernel32")]
        public static extern IntPtr LoadLibrary(
            string path);

        [DllImport("kernel32")]
        public static extern IntPtr GetProcAddress(
            IntPtr libraryHandle,
            string symbolName);

        [DllImport("kernel32")]
        public static extern bool FreeLibrary(
            IntPtr libraryHandle);
        #endregion

        public BattleNativeDelegates()
        {
            string[] libPaths = new string[] {Application.dataPath + LIBRARY_PATH_0};
            m_LibraryHandles = new List<IntPtr>();
            foreach(var libPath in libPaths)
            {
                m_LibraryHandles.Add(OpenLibrary(libPath));
            }
        }

        private IntPtr OpenLibrary(string path)
        {
            IntPtr handle = LoadLibrary(path);
            if (handle == IntPtr.Zero)
            {
                throw new Exception("Couldn't open native library: " + path);
            }
            return handle;
        }

        public void CloseAllLibraries()
        {
            for (int i = 0; i < m_LibraryHandles.Count; i++)
            {
                FreeLibrary(m_LibraryHandles[i]);
                m_LibraryHandles[i] = IntPtr.Zero;
            }
        }

        private static T GetDelegate<T> ( IntPtr libraryHandle, string functionName) where T : class
        {
            IntPtr symbol = GetProcAddress(libraryHandle, functionName);
            if (symbol == IntPtr.Zero)
            {
                throw new Exception("Couldn't get function: " + functionName);
            }
            return Marshal.GetDelegateForFunctionPointer(
                symbol,
                typeof(T)) as T;
        }
    }
}

In C# when you pass around the address of a function, if that is an instance method (which SetBattleUI() actually is) you are also passing around the this associated with that function address. You’re just not seeing it.

You can see the two parts here at the bottom of this intellisense window: one is data (Target), the other is the address (Method).

Over on your C landing side all you are recording is a simple typedef void ( __stdcall *SetRoundUI )(int);, so that’s why I don’t think you can get at instance methods (or their instance variables), which is what you report as your problem in the first post, right?

I would take it one step farther and say that when you observe the non-static field to be zero, you are probably reaching into some unspecified block of memory, which makes me surprised it doesn’t access fault.

Shouldn’t then it also 0 out and lose the references when I call the callback from the native side but on the main thread?
Because there everything is working fine.(It only breaks if I call the callback from a new spawned thread)

That would seem to be true. You have now exhausted my knowledge on the topic.

If it were my project, and it truly is working from the main thread, I would just move the heartbeat “pump” to C# and call your C code main update loop directly, if you’re saying that callbacks from C back to C# work fine in that condition.

1 Like

Guess I’ll be sticking to the static volatile variables for now and pray that it’s not too criminal to do so. Bajillion of thanks for your input and help!

1 Like