3

I am trying to implement an asynchronous interface from C# Unity to C++.

This is the exposed functions in C++:

struct Vector3 {
    float x;
    float y;
    float z;
};

extern "C" {
    DLLEXPORT void sync_test(void*(Vector3[], int));
    DLLEXPORT void async_test(void*(Vector3[], int));
}

They are implemented like this:

void do_work(void*(onResult)(Vector3[], int)) {
    int size = 30;
    Vector3* result = new Vector3[size];
    for (int i = 0; i < size; i++) {
        result[i] = { 5,(float)i,2 };
    }
    onResult(result, size);
    delete[] result;
}

DLLEXPORT void sync_test(void*(onResult)(Vector3[], int)) {
    do_work(onResult);
}

DLLEXPORT void async_test(void*(onResult)(Vector3[], int)) {
    std::thread thread(do_work, onResult);
}

This is how i use them in C#:

[DllImport("Isosurfaces.dll")]
static extern void async_test(Action<IntPtr, int> onResult);
[DllImport("Isosurfaces.dll")]
static extern void sync_test(Action<IntPtr, int> onResult);

// Use this for initialization
void Start () {
    sync_test(OnResult);
    //async_test(OnResult);
}

private void OnResult(IntPtr result, int size) {
    Vector3[] tris = GetTris(result, size);
    Debug.Log(tris[size - 1]);
}

Vector3[] GetTris(IntPtr result, int size) {
    Vector3[] tris = new Vector3[size];
    int vec3Size = Marshal.SizeOf(new Vector3());
    for (int i = 0; i < size; i++) {
        tris[i] = (Vector3)Marshal.PtrToStructure(new IntPtr(result.ToInt32() + (i * vec3Size)), typeof(Vector3));
    }
    return tris;
}

When running the project in unity, sync_test works flawlessly. The printed Vector3 matches the one created on the C++ side.

When calling async_test instead, Unity shuts down without an error message. Looking in the Editor.log I can't see any information that could be related to the shut down. Looking in upm.log in the same folder I find this:

{"level":"error","message":"[Unity Package Manager (Upm)]\nParent process [12276] was terminated","timestamp":"2018-08-20T13:37:44.029Z"} 

but this does not give me much of a context as to what happened.

I suspect it has something to do with my C++ code. I haven't programmed in C++ for a while so there could be some memory i forgot to free. But as of now the only memory i see that i've allocated is Vector* result in do_work and it is free'd right after the C# side is done processing the result.

EDIT: Changed delete result to delete[] result in do_work but Unity still crashes.

Tagor
  • 937
  • 10
  • 30
  • Your `do_work` function invokes *undefined behavior* because it uses `delete` instead of `delete[]` – UnholySheep Aug 20 '18 at 13:59
  • hmmm, then how come it works when being called by `sync_test`? – Tagor Aug 20 '18 at 14:01
  • One possible result of *undefined behavior* is that code works "correctly" in some situations. The word *undefined* means that you have no guarantee what will happen. – UnholySheep Aug 20 '18 at 14:02
  • [`thread::~thread()`](https://en.cppreference.com/w/cpp/thread/thread/~thread) calls `std::terminate` if it has associated joinable thread. – Igor Korzhanov Aug 20 '18 at 14:05
  • Yeah, now i remember! But unfortunately, using delete[] the same issue occurs. @UnholySheep – Tagor Aug 20 '18 at 14:06
  • I also don't see any call for `Marshal.GetFunctionPointerForDelegate` method which makes such callbacks reliable for interoperability code. – Alex F Aug 20 '18 at 14:12
  • @AlexF The C# client crashes before the callback is even invoked it seems. I tested by creating a file in the C# callback and it was never created. – Tagor Aug 20 '18 at 14:15
  • @IgorKorzhanov Could you elaborate a little? I am only creating one thread on the C++ side. – Tagor Aug 20 '18 at 14:16
  • @AlexF Thanks for mentioning `Marshal.GetFunctionPointerForDelegate` i did not now it was required. The example i am looking at did not use or mention it, i'll try it now. – Tagor Aug 20 '18 at 14:24
  • @Tagor number of threads is irrelevant here. Point is that the thread object terminates your application because it has associated joinable thread upon destruction. You have to [`detach()`](https://en.cppreference.com/w/cpp/thread/thread/detach) a thread object from the thread, so the thread is allowed to continue execution independently from a thread object. – Igor Korzhanov Aug 20 '18 at 14:29
  • @Tagor - also, this: https://stackoverflow.com/questions/22803600/when-should-i-use-stdthreaddetach – Igor Korzhanov Aug 20 '18 at 14:32
  • @IgorKorzhanov Thank you, it makes sense now. – Tagor Aug 20 '18 at 14:35

2 Answers2

6

async_test creates std::thread object which is then immediately destroyed, and std::thread::~thread() is called in the process.

According to cppreference.com, std::thread destructor will call std::terminate() if it has associated joinable thread.

Consider to add thread.detach(); line to async_test so created thread is no longer attached to the std::thread object.

Igor Korzhanov
  • 216
  • 1
  • 6
3

Freeing memory in C++:

Vector3* result = new Vector3;
delete result;

And

Vector3* result = new Vector3[size];
delete [] result;

Your result variable is an array so delete [] result should be used

Programmer
  • 121,791
  • 22
  • 236
  • 328