内存泄漏?传递 IEnumerable时数组到非托管函数作为 byte** 参数
这是分配和释放传递给非托管 dll 的托管数据句柄的正确方法吗?
有一个带有导出函数的非托管 dll,
void Function(byte** ppData, int N);
我需要将其传递给 IEnumerable
var handles = afids.Select(afid => GCHandle.Alloc(afid, GCHandleType.Pinned));
var ptrs = handles.Select(h => h.AddrOfPinnedObject());
IntPtr[] afidPtrs = ptrs.ToArray();
uint N = (uint)afidPtrs.Length;
Function(afidPtrs, N);
handles.ToList().ForEach(h => h.Free());
我得到托管内存泄漏,并在立即窗口中获取 sos.dll 给出了 gcroot
DOMAIN(00275030):HANDLE(Pinned):3ea2c0:Root: 17a8d190(System.Byte[])
函数定义是:
[DllImport("My.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern unsafe internal int Function(IntPtr[] ppData, int N);
控制台应用程序的错误代码片段:
static void Main(string[] args)
{
while (!Console.KeyAvailable)
{
IEnumerable<byte[]> data = CreateEnumeration(100);
PinEntries(data);
Thread.Sleep(900);
Console.Write(String.Format("gc mem: {0}\r", GC.GetTotalMemory(true)));
}
}
static IEnumerable<byte[]> CreateEnumeration(int size)
{
Random random = new Random();
IList<byte[]> data = new List<byte[]>();
for (int i = 0; i < size; i++)
{
byte[] vector = new byte[12345];
random.NextBytes(vector);
data.Add(vector);
}
return data;
}
static void PinEntries(IEnumerable<byte[]> data)
{
var handles = data.Select(d => GCHandle.Alloc(d, GCHandleType.Pinned));
var ptrs = handles.Select(h => h.AddrOfPinnedObject());
IntPtr[] dataPtrs = ptrs.ToArray();
Thread.Sleep(100); // unmanaged function call taking byte** data
handles.ToList().ForEach(h => h.Free());
}
控制台应用程序的正确代码片段:
static void PinEntries(IEnumerable<byte[]> data)
{
IEnumerable<GCHandle> handles = CreateHandles(data);
IntPtr[] ptrs = GetAddrOfPinnedObjects(handles);
Thread.Sleep(100); // unmanaged function call taking byte** data
FreeHandles(handles);
}
static IEnumerable<GCHandle> CreateHandles(IEnumerable<byte[]> data)
{
IList<GCHandle> handles = new List<GCHandle>();
foreach (byte[] vector in data)
{
GCHandle handle = GCHandle.Alloc(vector, GCHandleType.Pinned);
handles.Add(handle);
}
return handles;
}
static IntPtr[] GetAddrOfPinnedObjects(IEnumerable<GCHandle> handles)
{
IntPtr[] ptrs = new IntPtr[handles.Count()];
for (int i = 0; i < ptrs.Length; i++)
ptrs[i] = handles.ElementAt(i).AddrOfPinnedObject();
return ptrs;
}
static void FreeHandles(IEnumerable<GCHandle> handles)
{
foreach (GCHandle handle in handles)
handle.Free();
}
Is that the correct way to allocate and free handles to managed data passed to unmanaged dll?
There is unmanaged dll with exported function
void Function(byte** ppData, int N);
I need to pass it IEnumerable<byte[]> afids
var handles = afids.Select(afid => GCHandle.Alloc(afid, GCHandleType.Pinned));
var ptrs = handles.Select(h => h.AddrOfPinnedObject());
IntPtr[] afidPtrs = ptrs.ToArray();
uint N = (uint)afidPtrs.Length;
Function(afidPtrs, N);
handles.ToList().ForEach(h => h.Free());
I get managed memory leaks and getting sos.dll in Immediate Window gave gcroot
DOMAIN(00275030):HANDLE(Pinned):3ea2c0:Root: 17a8d190(System.Byte[])
Function definition is:
[DllImport("My.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern unsafe internal int Function(IntPtr[] ppData, int N);
Buggy code snippet for console application:
static void Main(string[] args)
{
while (!Console.KeyAvailable)
{
IEnumerable<byte[]> data = CreateEnumeration(100);
PinEntries(data);
Thread.Sleep(900);
Console.Write(String.Format("gc mem: {0}\r", GC.GetTotalMemory(true)));
}
}
static IEnumerable<byte[]> CreateEnumeration(int size)
{
Random random = new Random();
IList<byte[]> data = new List<byte[]>();
for (int i = 0; i < size; i++)
{
byte[] vector = new byte[12345];
random.NextBytes(vector);
data.Add(vector);
}
return data;
}
static void PinEntries(IEnumerable<byte[]> data)
{
var handles = data.Select(d => GCHandle.Alloc(d, GCHandleType.Pinned));
var ptrs = handles.Select(h => h.AddrOfPinnedObject());
IntPtr[] dataPtrs = ptrs.ToArray();
Thread.Sleep(100); // unmanaged function call taking byte** data
handles.ToList().ForEach(h => h.Free());
}
Correct code snippet for console application:
static void PinEntries(IEnumerable<byte[]> data)
{
IEnumerable<GCHandle> handles = CreateHandles(data);
IntPtr[] ptrs = GetAddrOfPinnedObjects(handles);
Thread.Sleep(100); // unmanaged function call taking byte** data
FreeHandles(handles);
}
static IEnumerable<GCHandle> CreateHandles(IEnumerable<byte[]> data)
{
IList<GCHandle> handles = new List<GCHandle>();
foreach (byte[] vector in data)
{
GCHandle handle = GCHandle.Alloc(vector, GCHandleType.Pinned);
handles.Add(handle);
}
return handles;
}
static IntPtr[] GetAddrOfPinnedObjects(IEnumerable<GCHandle> handles)
{
IntPtr[] ptrs = new IntPtr[handles.Count()];
for (int i = 0; i < ptrs.Length; i++)
ptrs[i] = handles.ElementAt(i).AddrOfPinnedObject();
return ptrs;
}
static void FreeHandles(IEnumerable<GCHandle> handles)
{
foreach (GCHandle handle in handles)
handle.Free();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您陷入了 Linq 陷阱,它的枚举器的行为不像集合。句柄被分配两次,第一次是当您使用ptrs.ToArray()时,再次是当您使用handles.ToList()时。明显的副作用是第一组句柄不会被释放。修复:
注意添加的 ToList() 以强制将枚举放入集合中。
You are falling into a Linq trap, its enumerators don't behave like a collection. The handles get allocated twice, first when you use ptrs.ToArray(), again when you use handles.ToList(). With the obvious side-effect that the first set of handles don't get freed. Fix:
Note the added ToList() to force the enumeration into a collection.