使用GCHandle获取.NET对象的地址(指针)

6

我通过以下方式成功获取了一个.NET对象的地址:

GCHandle objHandle = GCHandle.Alloc(obj,GCHandleType.WeakTrackResurrection);
int address = GCHandle.ToIntPtr(objHandle).ToInt32();  

我可以通过以下方式来调用对象:

Object obj = GCHandle.FromIntPtr(IntPtr(address)).Target;

目的是将地址存储在本地类中,并了解哪个本机对象与哪个.net对象相关。


据我所知,由于分配而导致地址不会改变,这是真的吗?还是有更好的想法来满足我的需求?


谢谢


有没有可能设置“固定对象”的值,例如:GCHandle.FromIntPtr(IntPtr(address))。Target = myNewObject? - Martin Ch
@MartinCh:我不知道设置“Target”属性是否合法。为什么不去SO问问呢? - ali_bahoo
4个回答

7

你需要将GCHandle固定,以防止对象在GC移动对象时发生移动,因此未固定的指针可能会变得无效。 固定对象可以停止它的移动:

GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();

完成后,您还需要释放句柄:

handle.Free();

3
微软表示:“包含非原始类型(不可移植类型)成员的实例无法被固定。”这就是为什么我使用 WeakTrackResurrection。 - ali_bahoo
请返回已翻译的文本:http://msdn.microsoft.com/zh-cn/library/1246yz8f(v=VS.80).aspx编译器错误: - ali_bahoo
1
我认为他是正确的,应该被固定。blittable怎么了?在MSDN上看到的评论:“如果您无法为类获取固定的GC句柄,则可能在类型定义中缺少一些[StructLayout(LayoutKind.*)]。” - Tim Lovell-Smith

6

正如 Tim 和 thecoop 所指出的,GCHandle.Alloc 方法可能会阻止垃圾回收,但是实际对象地址可能会发生变化,因为 GC 可能会移动对象,除非你固定对象。此外,你的代码正在使用 GCHandleType.WeakTrackResurrection,它甚至不能防止垃圾回收。 GCHandle.ToIntPtr 将给出可逆转非托管调用的句柄地址。实际对象地址将由 AddrOfPinnedObject 方法给出。

总之,我认为你的代码可以实现将.NET对象与非托管对象关联的目的。这是因为 GCHandle.To/FromIntPtr 会获取正确的 GCHandle,你可以通过它访问你的 .NET 对象(前提是其未被垃圾回收)。在我看来,实际对象地址是否更改都无关紧要。


我测试了一下创建了1000万个对象,然后在设置和获取地址变量时运行了代码。令人惊讶的是发现它并没有改变。这是因为它是对象句柄的地址,正如你所说的那样。那么我怎样才能防止对象被垃圾回收呢? - ali_bahoo
为什么不使用GCHandleType.Normal - 这将防止垃圾回收。 - VinayC
转念一想,这可能行不通——例如,如果您在方法中创建了GCHandle,通过ToIntPtr获取其地址并将其传递给非托管代码,然后执行handle.Free,因为它超出了范围,那么Free调用将使对象有资格进行GC。如果您处于这种情况下,我建议您可以创建一个静态字典/列表来保存您的对象,以防止垃圾回收。 - VinayC
我将在.NET对象内创建并持有GCHandle变量。因此,如果对象存在,则句柄也将存在。我使用了GCHandleType.Normal类型,但是随着对象的创建,内存稳定增长,因为句柄作为对象成员保留。但这是必须的,因为具有弱句柄类型的对象在调用强制GC.collection时肯定会被GC收集。 - ali_bahoo

2
你得到的实际上不是地址。
正如你所注意到的,它大部分时间似乎像一个地址,你可以通过使用GCHandle.FromIntPtr来调用对象。然而,有趣的问题是你使用了GCHandleType.WeakTrackResurrection。
如果你的弱引用对象被收集(因为只是由GCHandle弱引用),你仍然拥有IntPtr,并且可以将其传递给GCHandle.FromIntPtr()。如果你这样做,那么你会得到null,假设IntPtr还没有被回收。(如果IntPtr由于某种原因被CLR回收,那么你就会遇到麻烦。我不确定这是否会发生。)
如果你想要一个强引用对象,最好使用GCHandleType.Normal或GCHandleType.Pinned(如果需要在非托管代码中获取对象的地址)。
(要使用GCHandleType.Pinned,你的对象必须是原始类型,或者具有[StructLayout]属性,或者是这些对象的数组。)

1
据我所知,地址不会因为分配而改变,这是真的吗?
托管对象的地址确实会改变。垃圾回收器可以自由地移动内存中的对象。当垃圾回收器运行时,它会收集未使用的对象,然后重新排列剩余的对象以最小化托管堆的总体大小。
有没有人有更好的想法来满足我的目的?
我不确定您是否能找到一个好的方法来长时间保留托管对象的指针。可以通过固定内存中的对象并以此获得其地址,但在C#中,只能在单个方法中固定对象。
如果您解释一下获得指针后要做什么,那将有助于我们更详细地了解您的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接