PInvoke: Allocate memory in C++ and free it in C#
Asked Answered
T

2

6

We are using PInvoke to interop between C# and C++.

I have an interop struct as follows, with an identical layout C++ struct on the other side.

[StructLayout(LayoutKind.Sequential)]
public struct MeshDataStruct : IDisposable
{
    public MeshDataStruct(double[] vertices, int[] triangles , int[] surfaces)
    {
        _vertex_count = vertices.Length / 3;
        _vertices = Marshal.AllocHGlobal(_vertex_count*3*sizeof (double));
        Marshal.Copy(vertices, 0, _vertices, _vertex_count);
    }

    // .. extract data methods to double[] etc.

    private IntPtr _vertices;
    private int _vertex_count;

    public void Dispose()
    {
        if (_vertices != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(_vertices);
            _vertices = IntPtr.Zero;
        }
    }
}

Now I would like to add a second ctor

    public MeshDataStruct(bool filled_in_by_native_codee)
    {
        _vertex_count = 0;
        _vertices = IntPtr.Zero;
    }

and then write a method in C++ that allows C++ to fill in the data. This would allow us to use the same structure for input as well as output data...

However, as far as I understand it, AllocHGlobal is available in C# and C++/Cli, but not pure C++.

So my question is: How can I allocate memory in C++ such that I can safely free it on the C# side with a call to Marshal.FreeHGlobal(...)?

Tannie answered 2/11, 2015 at 16:6 Comment(1)
Use the Windows API GlobalAlloc, LocalAlloc functions and Heap functions: msdn.microsoft.com/en-us/library/windows/desktop/…. Also I don't recommend having two different languages try to figure out how each allocates or deallocates memory. Let each language be responsible for their own memory, with the only possible exception of usage of the Windows API heap allocation functions I mentioned earlier.Downbeat
D
5

This traditionally always ended up poorly, the Microsoft CRT created its own heap with HeapCreate() to service malloc/new calls in a C or C++ program. Can't deallocate such memory in C#, you don't have the heap handle.

That has changed however, starting with the CRT included with VS2012 (msvcr120.dll and up). It now uses the default process heap, the one returned by GetProcessHeap(). Also the one used by Marshal.Alloc/FreeHGlobal(). So you now have a shot at it, provided the native code doesn't use the debug allocator (crtdbg.h). Be careful throwing away that debug option.

The pinvoke marshaller was not changed, nor can it. If it has to release memory, like an array or string returned as a function return value, then it will call CoTaskMemFree(). It is not clear from your question which could apply. In case of doubt and if you have the choice in your native code then you can't go wrong with CoTaskMemAlloc(), paired to Marshal.FreeCoTaskMem() in your C# code.

Dorweiler answered 2/11, 2015 at 16:41 Comment(0)
Q
5

From the documentation:

AllocHGlobal is one of two memory allocation methods in the Marshal class. (Marshal.AllocCoTaskMem is the other.) This method exposes the Win32 LocalAlloc function from Kernel32.dll.

When AllocHGlobal calls LocalAlloc, it passes a LMEM_FIXED flag, which causes the allocated memory to be locked in place. Also, the allocated memory is not zero-filled.

So, you can call LocalAlloc from your unmanaged code to allocate memory, and Marshal.FreeHGlobal from your managed code to deallocate it. Likewise, LocalFree can be be used in unmanaged code to deallocate memory allocated with Marshal.AllocHGlobal.

As the documentation also intimates, you could do the same with CoTaskMemAlloc/CoTaskMemFree and Marshal.AllocCoTaskMem/FreeCoTaskMem.

Having said that, you are setting yourself up for a fall doing it this way. It is far cleaner to keep the allocation and deallocation in the same modules. Mixing an matching in this way is very likely to lead to great confusion over who is responsible for deallocating the memory.

Quade answered 2/11, 2015 at 16:25 Comment(6)
I do see your point, but it seems cleaner to me to always just have the interop struct's Dispose be responsible for the cleanup instead of having some structs with C# cleanup, others with C++ cleanup.Tannie
@Wilbert, then why not have the C# code know which side allocated the memory then call either the C# free or the C++ free (through pinvoke of course)?Biondo
The documetation of LocalFree says that it not safe to deallocate memory allocated with GlobalAlloc. Then, maybe the function to call to safely release memory allocated with Marshal.AllocHGlobal in managed code is GlobalFree in umanaged?Debut
@Debut You might think so, but AllocHGlobal is named misleadingly. As you can see clearly from the documentation that I reproduced in the answer, AllocHGlobal calls LocalFree.Quade
You surely mean LocalAlloc, I also verified it in the reference source. Thank you, I didn't read your message carefully.Debut
Er, yes, I was typing too fast!Quade
D
5

This traditionally always ended up poorly, the Microsoft CRT created its own heap with HeapCreate() to service malloc/new calls in a C or C++ program. Can't deallocate such memory in C#, you don't have the heap handle.

That has changed however, starting with the CRT included with VS2012 (msvcr120.dll and up). It now uses the default process heap, the one returned by GetProcessHeap(). Also the one used by Marshal.Alloc/FreeHGlobal(). So you now have a shot at it, provided the native code doesn't use the debug allocator (crtdbg.h). Be careful throwing away that debug option.

The pinvoke marshaller was not changed, nor can it. If it has to release memory, like an array or string returned as a function return value, then it will call CoTaskMemFree(). It is not clear from your question which could apply. In case of doubt and if you have the choice in your native code then you can't go wrong with CoTaskMemAlloc(), paired to Marshal.FreeCoTaskMem() in your C# code.

Dorweiler answered 2/11, 2015 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.