开发者

Memory leaks? in passing IEnumerable<byte[]> array to unmanaged function as byte** parameter

开发者 https://www.devze.com 2023-03-24 23:33 出处:网络
Is that the correct way to allocate and free handles to managed data passed to unmanaged dll? There is unmanaged dll with exported function

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开发者_StackOverflow社区(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();
}


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());
}

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:

        var handles = data.Select(d => GCHandle.Alloc(d, GCHandleType.Pinned)).ToList();
        var ptrs = handles.Select(h => h.AddrOfPinnedObject());
        IntPtr[] dataPtrs = ptrs.ToArray();
        handles.ForEach(h => h.Free());

Note the added ToList() to force the enumeration into a collection.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号