Search code examples
c#c++marshalling

Updating a C# array inside C++ without Marshal.Copy or Unsafe


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;
}

Solution

  • 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.