user588477
user588477

Reputation: 165

Memory Leak between C# and C++ data marshaling

Here is my sample code to describe the flow

C#

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct CppStruct
{
    public int n;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string data;

}

C++ struct

struct CppData
{
    int N;
    char Data[16];
};

C# pass a delegate to C++

public delegate void Get(ref int count, out IntPtr[] o); // I want to get an array of data

In C++

typedef void(__stdcall* GetPtr)(int* count, void** o); //fptr

GetPtrcall CSharp = the delegate passed from C#

// Use this callCSharp to callback to C#

void* ptr = nullptr;
int count = 0; 
callCSharp(&count, &ptr);

Now here is the critical point, When entering C# delegate

private static void FromCpp(ref int count, out IntPtr[] o)
{
    var d = new CppStruct();
    d.n = 1;
    d.data = "data";
    CppStruct[] ds = new CppCsharMapping[3] { d, d, d }; // Intended to put three elements as an array.
    count = 3; // Let C++ knows, there will be 3 elements back.
    int size = Marshal.SizeOf(d);
    IntPtr[] pntArr = new IntPtr[3];
    pntArr[0] = Marshal.AllocHGlobal(size);
    pntArr[1] = Marshal.AllocHGlobal(size);
    pntArr[2] = Marshal.AllocHGlobal(size);
    for (int i = 0; i < 3; i++)
    {
        Marshal.StructureToPtr(ds[i], pntArr[i], true);
    }

    o = pntArr; // !!!!!!!!!!!!!!!!!!!!!!!!!!!! this line cause leak.
    gPntArr = pntArr; //I used a static variable gPntArr to save pntArr for free this memory block after the return.
    return; 
}

// Something like below in another method triggered from Cpp. Make sure Cpp read the data back.

void Free()
{
    foreach(var m in gPntArr)
    {
        Marshal.FreeHGlobal(m);
    }
}

Now after C# returning, C++ can read back the data without any error.

CppData** back = (CppData**)ptr;
for (int i = 0; i < count; i++, back++)
{
// Print out the value
(*back)->N
(*back)->Data
}

BUT I found that by calling while(true){callCSharp(&ptr); Free();}, memory keeps increasing then I found o = pntArr; is the line causing this.

But I don't have any idea how to deallocate memory properly.

I cannot find a way to achieve my goal, Cpp calls C# delegate to get an array back(using void* is because I might need different types of data from C#, here is the sample I ask for a single type of array), so the sample above is what I was trying, maybe it is wrong but it worked.

May I know why this line cause a leak? Or anything I can refine or use another method

Upvotes: 0

Views: 381

Answers (1)

Charlieface
Charlieface

Reputation: 71593

The problem seems to be that you are passing the array back to C++ via the normal marshalling methods, but since this is part of a callback, the marshaller does not know to free this memory.

I would suggest custom marshalling the whole array:

IntPtr _gPntArr;
int _arrSize;

private static void FromCpp(ref int count, out IntPtr o)
{
    var d = new CppStruct();
    d.n = 1;
    d.data = "data";
    CppStruct[] ds = new CppCsharMapping[3] { d, d, d }; // Intended to put three elements as an array.
    count = ds.Length; // Let C++ knows, there will be 3 elements back.

    _arrSize = ds.Length  * Marshal.SizeOf<CppCsharMapping>();
    _gPntArr = Marshal.AllocHGlobal(_arrSize);
    int size = Marshal.SizeOf(d);
    for (int i = 0; i < _arrSize; i += Marshal.SizeOf<CppCsharMapping>())
    {
        var ptr = Marshal.AllocHGlobal(size)
        Marshal.WriteIntPtr(IntPtr.Add(_gPntArr, i), ptr);
        Marshal.StructureToPtr(ds[i], ptr, false);
    }

    o = _gPntArr;
}

void Free()
{
    if(_gPntArr == IntPtr.Zero)
        return;
    for (int i = 0; i < _arrSize; i += Marshal.SizeOf<CppCsharMapping>())
    {
        Marshal.FreeHGlobal(Marshal.ReadIntPtr(IntPtr.Add(_gPntArr, i)));
    }
    Marshal.FreeHGlobal(_gPntArr);
    _gPntArr = IntPtr.Zero;
}

Upvotes: 1

Related Questions