7

I am trying to pass a double array (its actually a std::vector, but converted at transfer) from a c++ dll into a c# script (unity). Using the approach outlined here https://stackoverflow.com/a/31418775.

I can successfully get the size of the array printing on my console in unity however I am not able to use "CoTaskMemAlloc" to allocate memory for the array since I am using Xcode and it doesnt seem to have COM.

For a little more background this array is part of a control for a GUI, c++ creates it and the user edits with the c# GUI - so the plan is to be able to pass the array back to c++ when it has been edited.

C++ code
extern "C" ABA_API void getArray(long* len, double **data){
    *len = delArray.size();
    auto size = (*len)*sizeof(double);
    *data = static_cast<double*>(CoTaskMemAlloc(size));
    memcpy(*data, delArray.data(), size);
}

C# code 
[DllImport("AudioPluginSpecDelay")]
private static extern void getArray (out int length,[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] out double[] array);

int theSize;
double[] theArray;
getArray(out theSize, out theArray);

If I leave out the code concerning the array, the int passes just fine. So I beleive the method to be the right one, its just getting around the lack of CoTaskMemAlloc.

Community
  • 1
  • 1
k9256
  • 73
  • 1
  • 3
  • are you saying you are not sure what to do on the c++ side, or, on the c# side? where is the problem? – Fattie Mar 25 '16 at 17:15
  • Hey, the problem is with CoTaskMemAlloc which allocates the memory for the array (and it HAS to be CoTaskMemAlloc, i tried with other memory allocations and it didn't work). It does not exist on mac! as yms pointed out below GLib is supposedly the alternative for mac. I am open to other methods that do not use GLib :) – k9256 Mar 25 '16 at 17:22
  • 1
    You could also do all memory management on the C# side :) – Luaan Mar 25 '16 at 17:49
  • hi @k9256 - I wanted to ask you something. Just FWIW, *what are you actually trying to do*? FYI: it's extremely common with Unity that: experienced programmers (like yourself), who are fooling around with Unity, "accidentally" as it were go down the path of trying to do something, unaware that there is a ridiculously simple way to achieve said thing in the Unity milieu. Just as a curiosity what are you actually trying to do? Cheers – Fattie Mar 26 '16 at 15:11
  • Hey Joe, I am currently making a spectral delay audio plugin using the unity native audio SDK. The array that is going between c++/c# are delay time arrays so the user can choose different times for each bin of the FFT(which is in C++) with a GUI. Its my first time using the SDK, and tbh I haven't used C++ a lot. So learning best practices with both at the same time is challenging! – k9256 Mar 26 '16 at 16:57

2 Answers2

6

You should be able to allocate memory in XCode using malloc and free it in C# using Marshal.FreeCoTaskMem. To be able to free it however, you need to have the IntPtr for it:

C++ code

extern "C" ABA_API void getArray(long* len, double **data)
{
    *len = delArray.size();
    auto size = (*len)*sizeof(double);
    *data = static_cast<double*>(malloc(size));
    memcpy(*data, delArray.data(), size);
}

C# code

[DllImport("AudioPluginSpecDelay")]
private static extern void getArray(out int length, out IntPtr array);

int theSize;
IntPtr theArrayPtr;
double[] theArray;

getArray(out theSize, out theArrayPtr);
Marshal.Copy(theArrayPtr, theArray, 0, theSize);
Marshal.FreeCoTaskMem(theArrayPtr);

// theArray is a valid managed object while the native array is already freed

Edit

From Memory Management I gathered that Marshal.FreeCoTaskMem would most likely be implemented using free(), so the fitting allocator would be malloc().

There are two ways to be really sure:

  1. Allocate the memory in CLI using Marshal.AllocCoTaskMem, pass it to native to have it filled, and then free it in the CLI again using Marshal.FreeCoTaskMem.
  2. Leave it as it is (native allocates memory with malloc()), but do not free the memory in CLI. Instead, have another native function like freeArray(double **data) and have it free() the array for you once CLI is done using it.
Thomas Hilbert
  • 3,559
  • 2
  • 13
  • 33
  • This is absolutely perfect, I was getting far too caught up in following the other method perfectly to think about doing this! Thanks. – k9256 Mar 25 '16 at 19:08
  • 2
    @k9256 I am not sure this is correct... you will be allocating with one function and de-allocating with a different (possibly unrelated) function. At least try to make sure that your code is not leaking memory, even if it looks like "it works" – yms Mar 26 '16 at 04:41
  • Just checked, the memory usage for Unity does creep up when I am using the GUI I have made. I tried adding free(*data) in c++, but this causes a crash. Do you suggestions on where to look to stop memory leakage? – k9256 Mar 26 '16 at 12:25
  • I am now not sure about memory leakage, Unity seems to creep up on its own without my plugin present. Would Marshal.FreeCoTaskMem(arrayPtr) not free the memory allocated by C++? I know that I should be using CoTaskMemAlloc in C++, but I am not entirely clear on the difference – k9256 Mar 26 '16 at 12:50
  • I struggle to see why you wouldn't have all malloc/free pairs be completely self-contained in the iOS side. For that matter - who has used memory management in iOS for what 10 years? Just program "properly" in a modern manner you know?? :O – Fattie Mar 26 '16 at 15:12
  • 1
    As the first approach was to free the memory in CLI, programming it "properly" - which I understand to mean ``new`` and ``delete`` - won't work, because they contain extra logic the CLI does not know about. So the solution had to be restricted to plain memory allocation. Feel free to post an answer proposing a different approach. – Thomas Hilbert Mar 26 '16 at 15:32
4

I am not an expert on Unity, but it seems that Unity relies on Mono for it's C# scripting support. Take a look at this documentation page:

Memory Management in Mono

We can assume from there that you will need to have platform-dependent code on your C++ side, you will need to use CoTaskMemAlloc/CoTaskMemFree in Windows and GLib memory functions g_malloc() and g_free() for Unix (like iOS, Android etc).

If you have control over all your code, C++ and C#, the easiest way to implement this would be to do all the memory allocation/deallocation in the C# script.

Sample code (untested):

//C++ code

extern "C" ABA_API long getArrayLength(){
    return delArray.size();
}

extern "C" ABA_API void getArray(long len, double *data){
    if (delArray.size() <= len)
        memcpy(data, delArray.data(), delArray.size());
}

// C# code
[DllImport("AudioPluginSpecDelay")]
private static extern int getArrayLength(); 

[DllImport("AudioPluginSpecDelay")]
private static extern void getArray(int length,[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] array);

int theSize = getArrayLength();
double[] theArray = new double[theSize];
getArray(theSize, theArray);
Community
  • 1
  • 1
yms
  • 10,361
  • 3
  • 38
  • 68
  • Hey, Thanks for the doc link, I am attempting to get GLib now. I am not hugely confident in C++ so I wish I didnt have to, but it seems unavoidable! – k9256 Mar 25 '16 at 17:23