当我使用GCHandle.Alloc(o)
和GCHandle.ToIntPtr()
时,我得到了一个.NET对象的固定地址,该地址可用于以后从本机代码中引用。很好,花花公子。
但是,在以后的某个时间点,可以将同一对象再次发送到native。我没有任何先前分配的GCHandle
类型的概念,必须重新分配。也可以。
问题是我需要在本机代码中跟踪唯一实例。我无法比较从创建的System.IntPtr
返回的GCHandle
,因为它们是不同的(并不奇怪)。
在本机代码中,我是否可以通过某种方式来比较分别void*
独立的两种GCHandle.Alloc
类型?
答案 0 :(得分:2)
GCHandle.ToIntPtr()
甚至对于同一对象也不是唯一的。可悲的是,内部GCHandle.ToIntPtr()
不会检查内部表中是否已经存在相同的对象,因此对于每个GCHandle,它都会返回一个不同的IntPtr
。这里没有简单的解决方案。
您可以在其中放置一个ID(因为在托管对象中没有预先构建的唯一对象标识符)。显然,您甚至可以保持托管对象固定,然后用GCHandle.AddrOfPinnedObject()
传递其“真实”地址。这样,GC不会移动它。这仅适用于可固定对象(它们是.NET所有对象类型的子集)。请注意,长时间固定对象并不容易,因为这会使GC的工作更加困难。没有其他简单的方法。
最后,公认的解决方案是“手动”处理标识符的分配:存储在(可能是static
)Dictionary<,>
/ 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
)。