Reputation: 165
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
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