通过GCHandle.ToIntPtr()在本机代码中比较.NET实例

时间:2018-07-17 03:12:32

标签: .net

当我使用GCHandle.Alloc(o)GCHandle.ToIntPtr()时,我得到了一个.NET对象的固定地址,该地址可用于以后从本机代码中引用。很好,花花公子。

但是,在以后的某个时间点,可以将同一对象再次发送到native。我没有任何先前分配的GCHandle类型的概念,必须重新分配。也可以。

问题是我需要在本机代码中跟踪唯一实例。我无法比较从创建的System.IntPtr返回的GCHandle,因为它们是不同的(并不奇怪)。

在本机代码中,我是否可以通过某种方式来比较分别void*独立的两种GCHandle.Alloc类型?

1 个答案:

答案 0 :(得分:2)

GCHandle.ToIntPtr()甚至对于同一对象也不是唯一的。可悲的是,内部GCHandle.ToIntPtr()不会检查内部表中是否已经存在相同的对象,因此对于每个GCHandle,它都会返回一个不同的IntPtr。这里没有简单的解决方案。

您可以在其中放置一个ID(因为在托管对象中没有预先构建的唯一对象标识符)。显然,您甚至可以保持托管对象固定,然后用GCHandle.AddrOfPinnedObject()传递其“真实”地址。这样,GC不会移动它。这仅适用于可固定对象(它们是.NET所有对象类型的子集)。请注意,长时间固定对象并不容易,因为这会使GC的工作更加困难。没有其他简单的方法。

最后,公认的解决方案是“手动”处理标识符的分配:存储在(可能是staticDictionary<,> / ConcurrentDictionary<,> / ConditionalWeakTable<,>中传递给本机代码的对象列表以及一个标识符。前两个将保留对存储在其中的对象的强引用,以便GC不会收集它们,第三个将保留较弱的引用,以便在不再引用该对象时GC可以收集它们。然后创建一个使用该集合的方法,然后调用本机方法。

我将添加一些(完全未经测试的)代码来执行此操作(两个版本,强引用和弱引用):

// Strong-reference of the tracked objects: they won't be freed by .NET
// Not thread safe against adding/removing the same key from two different threads!
public class ManagedObjectTracker<TKey> where TKey : class
{
    private readonly ConcurrentDictionary<TKey, IntPtr> dictionary = new ConcurrentDictionary<TKey, IntPtr>();
    private readonly ConcurrentDictionary<IntPtr, TKey> reverseDictionary = new ConcurrentDictionary<IntPtr, TKey>();
    private long lastId;

    // Will always return a handle!
    public bool TryAdd(TKey key, out IntPtr handle)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        bool added = false;

        handle = dictionary.GetOrAdd(key, x =>
        {
            // No guarantee lastId will be contiguous. Some values could be discarded by GetOrAdd
            // if there is heavy concurrency
            added = true;
            return (IntPtr)Interlocked.Increment(ref lastId);
        });

        if (added)
        {
            reverseDictionary[handle] = key;
        }

        return added;
    }

    public bool TryGetKey(IntPtr handle, out TKey key)
    {
        return reverseDictionary.TryGetValue(handle, out key);
    }

    public bool TryGetHandle(TKey key, out IntPtr handle)
    {
        return dictionary.TryGetValue(key, out handle);
    }

    public bool TryRemoveByKey(TKey key, out IntPtr handle)
    {
        if (dictionary.TryRemove(key, out handle))
        {
            reverseDictionary.TryRemove(handle, out key);
            return true;
        }

        return false;
    }

    public bool TryRemoveByHandle(IntPtr handle, out TKey key)
    {
        if (reverseDictionary.TryRemove(handle, out key))
        {
            dictionary.TryRemove(key, out handle);
            return true;
        }

        return false;
    }
}

// Weak-reference of the tracked objects: they won't be freed by .NET
// Not thread safe against adding/removing the same key from two different threads!
public class WeakManagedObjectTracker<TKey> where TKey : class
{
    private readonly ConditionalWeakTable<TKey, SelfDisposingGCHandle> dictionary = new ConditionalWeakTable<TKey, SelfDisposingGCHandle>();

    // Will always return a handle!
    public bool TryAdd(TKey key, out IntPtr handle)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        SelfDisposingGCHandle handle2 = dictionary.GetOrCreateValue(key);

        if (handle2.IntPtr == IntPtr.Zero)
        {
            handle2.Swap(key);
            handle = handle2.IntPtr;
            return true;
        }

        handle = handle2.IntPtr;
        return false;
    }

    public bool TryGetKey(IntPtr handle, out TKey key)
    {
        GCHandle handle2 = GCHandle.FromIntPtr(handle);

        key = (TKey)handle2.Target;

        return key != null;
    }

    public bool TryGetHandle(TKey key, out IntPtr handle)
    {
        SelfDisposingGCHandle handle2;

        if (!dictionary.TryGetValue(key, out handle2))
        {
            handle = IntPtr.Zero;
            return false;
        }

        handle = handle2.IntPtr;
        return true;
    }

    public bool TryRemoveByKey(TKey key, out IntPtr handle)
    {
        if (TryGetHandle(key, out handle))
        {
            dictionary.Remove(key);
        }

        return false;
    }

    public bool TryRemoveByHandle(IntPtr handle, out TKey key)
    {
        if (TryGetKey(handle, out key))
        {
            dictionary.Remove(key);
            return true;
        }

        return false;
    }

    private sealed class SelfDisposingGCHandle : IDisposable
    {
        public GCHandle handle;

        public SelfDisposingGCHandle()
        {

        }

        public SelfDisposingGCHandle(object value)
        {
            handle = GCHandle.Alloc(value, GCHandleType.Weak);
        }

        public IntPtr IntPtr
        {
            get
            {
                return GCHandle.ToIntPtr(handle);
            }
        }

        public GCHandle Swap(object value)
        {
            GCHandle handle2 = handle;
            handle = GCHandle.Alloc(value, GCHandleType.Weak);
            return handle2;
        }

        ~SelfDisposingGCHandle()
        {
            if (handle.IsAllocated)
            {
                handle.Free();
            }
        }

        // Questo codice viene aggiunto per implementare in modo corretto il criterio Disposable.
        public void Dispose()
        {
            if (handle.IsAllocated)
            {
                handle.Free();
            }

            GC.SuppressFinalize(this);
        }
    }
}

这里的想法是,给定object,我们将获得类型IntPtr的句柄,该句柄可以传递给本机代码,给定IntPtr,我们可以获得{{1 }}。第一个版本(object)将使跟踪的对象保持活动状态。它的实现生成唯一的ManagedObjectTracker<>,它们不是内存地址,而只是自动递增id。第二个版本(IntPtr)不会使跟踪的对象保持活动状态,该对象可以由GC释放,并保存对象的WeakManagedObjectTracker<>(使用GCHandle)。