Search code examples
c#arrayspinvoke

Marshaling arrays in C# - why use AllocCoTaskMem vs new []?


I'm creating a C# facade for a C++ device library for our project, and this is my first P/Invoke rodeo. Consider the following declaration:

[DllImport(EzSensorEnuMDll, EntryPoint = "VDEIOrM_EnumConnected")]
public static extern int VDEIOrM_EnumConnected(int AMaxEnu, [In,Out] tVDEnu_IOrM[] AEnu_IOrM);

I naively called this method like this:

var sensors = new tVDEnu_IOrM[MaxEnumeratedSensors];
var result = VDEIOrM_EnumConnected(MaxEnumeratedSensors, sensors);

And it worked just fine. While researching an unrelated method call, I stumbled across a P/Invoke example where the programmer made a call to a method that also accepted an array, but he used some memory allocation functions that I was previously unaware of. It looked something like this:

var buffer = Marshal.AllocCoTaskMem(bufferSize);
MethodCall(buffer);
// do some things
Marshal.FreeCoTaskMem(buffer);

So my question is - why/when would I use the memory allocation functions over simply newing up an array and passing it? Have I simply not run into a problem out of sheer luck? I realize the P/Invoke declaration would need to change to accept an IntPtr, but is there any reason to prefer one methodology over the other?


Solution

  • Your code looks fine, no reason to assume it cannot work.

    The last snippet gives very little insight in why the author preferred to do it this way. AllocCoTaskMem() allocates from the COM interop heap. Its primary role is to allow the client and server code to agree on which heap they use. Necessary when, say, the server allocates memory that needs to be freed by the client. Or the other way around.

    Not the case here, the client both allocates and releases memory. Just like you do. Do note that this code looks pretty risky, the callee cannot know how much memory was allocated by the caller so can easily read or write beyond the end of the buffer. This can only get to a good end if bufferSize is dictated by the api contract.

    There are a few corner-cases where it might be preferable to use unmanaged memory:

    • the called function runs for a long time and is used in a program that uses threading. Not having to pin the buffer can then be advantageous to GC health.
    • the called function stores the passed pointer and uses it later, beyond the function call. Advantage of unmanaged memory is that it is stable, its address cannot change. Unlikely here, you'd see an additional api call to tell the native code to stop using the buffer.
    • the called function needs to re-allocate the buffer with CoTaskMemRealloc(). Definitely not the case here, it would pass the pointer with the ref keyword.

    Not compelling reasons, best thing here is to assume that the author was unsure about how to do this correctly.