Using a Callback/Delegate inside a C++ thread in a DLL.

I’ve written a threaded C++ DLL to handle some heavy lifting in the background.

Unity starts a thread inside the DLL (and then detaches the thread to run on it’s own). The thread receives data from unity occasionally without any issues, but when the thread sends information back to unity, the editor crashes without notice (if any of the data sent is used).

The program works without any issues when there is just a Debug.Log(values) call inside the Delegate/Callback from the C++ thread to the unity script, but as soon as I change any variables (including exclusively value types) Unity crashes.

I’ve tried creating a “lock” inside the Delegate without any improvement. I never access any unity objects, however the script does extend from Monobehaviour.

Stripped down code:

using UnityEngine;
using System.Collections.Generic;
using System;
using System.Runtime.InteropServices;

public class CallbackTest : MonoBehaviour
{
    public delegate void UpdateObjectsOnScreenDelegate(int ID, float rotation, float xPos, float yPos);

    [DllImport("ObjectRecognition")]
    private static extern bool StartDLLThread(UpdateObjectsOnScreenDelegate fp, int n, int m);

    [DllImport("ObjectRecognition")]
    private static extern void SendPacketUpdate(int[] ID, float[] x,float[] y, int length);

    class TouchPacket
    {       
int somestuff;
    }

    TouchPacket receivedPacket = new TouchPacket();

    void Awake()
    {

        if (StartDLLThread(new UpdateObjectsOnScreenDelegate(this.UpdateObjectsOnScreen), 10, 20))
        {
            Debug.Log("SUCCESS");
        }
        else
        {
            Debug.Log("Failed");
        }
    }


    public void SendPacket()
    {
        if (receivedPacket.ID.Count > 0)
        {
            SendPacketUpdate(receivedPacket.ID.ToArray(), receivedPacket.posX.ToArray(), receivedPacket.posY.ToArray(), receivedPacket.ID.Count);
        }
    }


    void UpdateObjectsOnScreen(int newID, float newRotation, float xPos, float yPos)
    {
            Debug.Log("Delegate fIred");

    }
}
extern "C" {

    void MainThread() {
        while (ThreadRunning) {
       
            if (data != NULL) {
                Calculations((*data));
updateObjects((some int), (some float), (some float), (some float));
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(30));

            //Lock the thread so we can get updates on the newest packet
            m1.lock();
            UpdateData();
            m1.unlock();
        }
    }
    void UpdateData() {
        if (newTouches.size() > 0) {
            if (data!= NULL) {
                (*data).clear();
            }
            newData= newPacket;
            data = &newData;
            newPacket.clear();
        }
    }

    bool StartDLLThread(UNITYCB updateCB)
    {

        updateObjects = updateCB;

        MainThreadRunning= true;

        //Start making calculations on received data
        thread = std::thread(MainThread);
        thread.detach();

        if (thread)
        {
            return true;
        }
        return false;
    }

    void SendPacketUpdate(int ID[], float posX[], float posY[], int length) {
        if (!touchUpdating) {
            touchUpdating = true;
            PacketThread= std::thread(PacketCalculationThread, ID, posX, posY, length);
            PacketThread.detach();
        }

    }

    void PacketCalculationThread(int ID[], float posX[], float posY[], int length) {
        //Set check for all existing touches to false (as in we didn't find any of the touches waiting in the queue)
        std::vector<bool> foundExisting;
        foundExisting.resize(length, false);
       
        //Lock this thread in so that it doesn't mess with the memory while the other thread is trying to grab it
        m1.lock();
//Some Calculations to the packet and changing the data to recognizable state
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
        touchUpdating = false;
        m1.unlock();

    }

  }

I was a fool. I wasn’t creating an instance of the delegate, so it never had access to the variables. It only had access to its local heap.
Better explanation: