Home > Enterprise >  How .Net CLR deals with memory returned from P/Invoke call?
How .Net CLR deals with memory returned from P/Invoke call?

Time:08-14

I have a .Net 4.6 C# GUI app that uses a native DLL. I would like to process data in the native code and return memory allocated on the native side back to C#.

I've read various questions on StackOverflow and links from around the web but there are some missing pieces I want to ensure, and potentially gather all the relevant interop answers in one place.

Case 1:

[DllImport("Native.dll")]
public static extern void GetStringA(
    [MarshalAs(UnmanagedType.LPStr)]
    out string str);
  • This one as I understand from the following documentation will be copied first and the original buffer will be freed using CoTaskMemFree.

https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
Windows Specific For [Out] strings the CLR will use CoTaskMemFree by default to free strings or SysStringFree for strings that are marked as UnmanagedType.BSTR.

Case 2:

[DllImport("Native.dll")]
public static extern void GetStringW(
    [MarshalAs(UnmanagedType.LPWStr)]
    out string str);
  • Will CLR call CoTaskMemFree on str or is it going to use the existing buffer and call CoTaskMemFree on a later GC cycle?
    (Notice the LPWStr)

Case 3:

[DllImport("Native.dll")]
public static extern void GetStringA(
    [MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 1)]
    out string str,
    out int length);
  • Will CLR take SizeParamIndex into account while marshalling back or just look for null termination?

Case 4:

[DllImport("Native.dll")]
public static extern void GetInts(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    out int[] array,
    out int length);
  • Will CLR call CoTaskMemFree on array or is it going to use the existing buffer and call CoTaskMemFree on a later GC cycle? Documentation talks about CoTaskMem* functions for strings but I couldn't see info on whether it handles LPArray the same way.
  • Will CLR take SizeParamIndex into account here while marshalling back or is it only effective for input parameters?

Case 5:

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string str;
    [MarshalAs(UnmanagedType.LPArray)]
    public int[] ints;
}

[DllImport("Native.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetStructs(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    out MyStruct[] array,
    out int length);
  • Can I count on CLR to handle complex types properly the same way it handles simple types?
    Is it going to recursively marshal and copy/reuse reference types the way I naturally expect?

TLDR; how do I handle memory management & ownership fully correct while using native interop? (FYI I author the native DLL as well as the GUI app.)

CodePudding user response:

Case 1:

The key point you are missing is the docuementation for CoTaskMemFree

Frees a block of task memory previously allocated through a call to the CoTaskMemAlloc or CoTaskMemRealloc function.

If your function does not allocate strings that way (for example it uses new or malloc) then the memory does not get freed and will leak. You must make sure to free the memory buffer using the same mechanism that is used to allocate it.

The marshaller does not magically know what allocation strategy you used. Ideally your C code would provide a FreeMemory function.

Case 2:

CoTaskMemFree is called immediately on the return of the function, after the pointer is copied into the out string. The marshaller does not get involved at all once your function has finished.

Case 3:

As far as I am aware, SizeParamIndex is only used for arrays, not strings, so null-termination will be used. Instead you can marshal this as a byte[] and copy it out afterwards.

[DllImport("Native.dll")]
public static extern void GetStringA(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    out byte[] str,
    out int length);

Case 4:

No, the marshaller will not free the array at all. You need to free the array yourself.

Case 5:

Yes, the marshaller will handle nested structs. But there are some things that cannot be done. For example, SizeParamIndex doe not work on struct members or on arrays inside structs. SizeConst still works, as does LPArray.

I suggest you specify the CharSet in your specific example.


I would suggest firstly, that you reconsider the reasons you are using C in the first place, and whether it is possible to write that code in C#.

Secondly, if possible use BSTR and SAFEARRAY, both of which the marshaller can fully handle in all cases, including nested arrays and arrays of strings.

If that is not possible, always try to allocate array buffers from C# and pass them in as [Out] parameters, as then the marshaller can allocate an unmanaged buffer (or pin it if blittable) in order for C to write into. The marshaller will copy and free (or unpin) that buffer automatically

  • Related