I am wanting to update an array that was created inside C#, and then pass a pointer to that array over to C++ and let C++ populate the indexes, to be used back in C#. Right now I am Using a Marshal.Copy() to accomplish this task, but I would like to avoid the potentially unnecessary copy, and call back to c++ to release the array. Is this even possible?
These array are floats and ints, for geometric mesh data.
My current usage (working and not what I want to use)
C#
IntPtr intptr=new IntPtr();
int count = 0;
PopulateArray(ref intptr, ref count);
float[] resultVertices = new float[count];
Marshal.Copy(intptr, resultVertices, 0, count);
C++
extern "C" __declspec(dllexport) bool PopulateArray(float** resultVerts, int* resultVertLength){
*resultVerts = new float[5]{0.123f, 3.141529f, 127.001f, 42.42f, 0};
int myX = 5;
*resultVertLength = myX;
return true;
}
The only safe way to have C++ code update a managed C# array is to pin the array. Otherwise, it's possible for the garbage collector to try to move the array while the native code is running. You can do this with a GCHandle object.
int count = 5;
float[] resultVertices = new float[count];
GCHandle handle = GCHandle.Alloc(resultVertices, GCHandleType.Pinned);
IntPtr address = handle.AddrOfPinnedObject();
PopulateArray(address, count);
handle.Free();
It can also be done with unsafe code, which is somewhat more intuitive to read and remember:
int count = 5;
float[] resultVertices = new float[count];
unsafe
{
fixed(float* ptr = resultVertices)
{
PopulateArray(ptr, count);
}
}
Another alternative is to have C# allocate an unmanaged chunk of memory and pass that to the C++ method. This is better than what you're doing because you are not placing the responsibility of allocation/deallocation in the C++ library code and instead keeping that all in your C#. I know you want to avoid the coy but sometimes doing the copy is more performant than pinning objects, but it depends on how large they are. I recommend you do performance testing to determine which is best for your situation.
int count = 5;
float[] resultVertices = new float[count];
IntPtr unmanagedMemory = Marshal.AllocHGlobal(count * Marshal.SizeOf(typeof(float)));
PopulateArray(unmanagedMemory, count);
Marshal.Copy(unmanagedMemory, resultVertices, 0, count);
In all these scenarios you should set your C++ code to operate like this:
extern "C" __declspec(dllexport) bool PopulateArray(float* resultVerts, int vertLength)
{
resultVerts[0] = 0.123f;
// fill out the rest of them any way you like.
return true;
}
If the array size is variable, then I recommend having a separate C++ method that calculates the size and returns it rather than having the C++ method allocate the memory.
If you are willing to allow C# to allocate the array (probably a safer alternative) then you could do this behavior with standard PInvoke attributes.
Change your C++ declaration to:
extern "C" __declspec(dllexport) bool PopulateArray(float resultVerts[], int resultVertLength)
and your C# declaration to:
[DllImport("Win32Library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool PopulateArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] resultVerts, int resultVertLength);
Your usage from the C# side would then change to:
var resultVertices = new float[5];
PopulateArray(resultVertices, resultVertices.Length);