0

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;
}
  • 1
    Why can't you use unsafe code? You already call C++ – Manuel Amstutz Dec 02 '15 at 18:54
  • *Is this even possible?* -- Why would you think it's not possible? Can't C# call Windows API functions using `pinvoke` that do exactly as you describe, i.e. C# passes a pointer to a buffer, and the API fills the buffer? – PaulMcKenzie Dec 02 '15 at 18:59
  • Yes, that's possible. Doesn't require unsafe code either. What kind of help you need to get there is *completely* unguessable. – Hans Passant Dec 02 '15 at 19:02
  • Manuel Amstutz This code will be getting ported to Unity3D at some point, and Unity can have issues building Unsafe C# to some platforms. PaulMcKenzie I know it is functionally possible but I would prefer doing it without having to do excessive amounts of data copying several times a second. – Brandon Farrell Dec 02 '15 at 19:04
  • If you could post some sample code showing how you are currently accomplishing this via `Marshal.Copy` that would provide way to see if this can be done. – nicholas Dec 02 '15 at 19:05
  • @BrandonFarrell In your code, who is responsible for calling `delete[ ]`? – PaulMcKenzie Dec 02 '15 at 19:14
  • @ nicholas It is pretty boiler plate from MSDN examples all I am doing is getting a pointer, and passing that to c++ and having c++ update what it is pointing to. then do a marshal.copy on that. – Brandon Farrell Dec 02 '15 at 19:15
  • @PaulMcKenzie not directly applicable to my question but C# calls a release. One of the things I am trying to avoid. – Brandon Farrell Dec 02 '15 at 19:17
  • @BrandonFarrell No. C# is not C++. When you call `new[ ]` in C++, you have to call `delete[ ]` at some point. It is not the same thing as a C# `new`. Also, I don't understand about data copying -- you create the buffer in C#, pass a pointer to this buffer to C++, have C++ fill it in. Done. Where is the excessive copying? – PaulMcKenzie Dec 02 '15 at 19:18
  • @PaulMcKenzie yes, C# calls the extern function in c++ that releases that data. With Marshal.Copy, takes the naitive floats, and migrates it into manged C# float[] – Brandon Farrell Dec 02 '15 at 19:21
  • @BrandonFarrell, can you change the call signature on the C++ side? – nicholas Dec 02 '15 at 19:30
  • @nicholas if i am understanding your question correctly, yes. I am writing both the C++ and C# implementations. – Brandon Farrell Dec 02 '15 at 19:32

2 Answers2

5

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.

Erik
  • 5,355
  • 25
  • 39
1

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);
nicholas
  • 2,969
  • 20
  • 39