阅读里希特J书的“手动监视和控制对象的生命周期”一节。 Jeffrey说,有两种方法可以使用GCHandle类控制对象的生命周期:
他说,两种方式都可以用来将托管对象传递给非托管代码。他试图解释,开发人员应该使用GCHandleType.Normal标志调用Alloc。我真的不明白正常标志使用的解释。在这两种方式中,我们都不允许GC收集对象,这些对象在GC描述符表中有这样的标记,但是在Pinned的情况下,我们还防止在垃圾收集期间移动这些对象。据我所知,在普通模式的情况下,不是直接引用(内存地址)传递给非托管代码,而只是传递给GC-descriptors表的索引。而当非托管代码回调托管代码时,则该索引将转换为当前/实际地址。我脑子里乱七八糟,谷歌和微软几乎没有细节信息,只能复制粘贴。
我的问题:
答案 0 :(得分:4)
如果在使用GCHandleType.Normal
创建句柄之前将对象引用传递给本机代码是不安全的,那么在创建这样的句柄之后它将是不安全的,因为非托管代码需要稳定的指针。因此,GCHandleType.Normal
句柄对非托管代码没有任何作用。我认为这是一个文档错误,建议不然。
GCHandleType.Normal
来创建不会死的对象。例如,某些Timer
类使自己的实例保持活动状态,以便在删除最后一个引用时计时器不会停止。
据我所知,在普通模式下,不是直接参考 (内存地址)传递给非托管代码,但只是索引 GC描述符表。
这不可能是真的,因为在发生PInvoke时,没有足够的信息来判断GCHandle
是否与您想要传递的对象相关联。即使它想要,马萨勒也无法做到这一点。另外,非托管代码对句柄表条目有什么作用?它不明白。句柄表是CLR内部。
某些应用程序root(非弱)引用托管中的对象 堆,没有更多的根。这是否意味着,相应的条目 GC-descriptors表将使用GCHandleType.Normal标志?容貌 不,因为杰弗里说,“GC甚至不能删除objs 可能是没有来自应用程序代码的引用“。但如果没有, 这个表条目会有哪些标志?
它具有您在创建GCHandle
时传入的标志。该表中只有条目GCHandle
。不跟踪普通对象。
答案 1 :(得分:3)
来自GCHandle的文档:
提供从非托管内存访问托管对象的方法。
如果您打算从非托管代码访问该对象,则需要将该对象固定。为了获得固定对象,必须可以将其编组为非托管内存。
如果你只需要一个不透明的句柄传递给非托管代码,以便非托管代码可以再次传回它而不访问它,那么你不需要一个固定对象,但你仍然需要确保它不被垃圾收集器删除。
考虑这个课程:
public class MyClass
{
DateTime dt = DateTime.Now;
}
如果你试图像这样得到一个固定的句柄:
MyClass o = new MyClass();
GCHandle h = GCHandle.Alloc(o, GCHandleType.Pinned);
您将收到以下消息的异常:
对象包含非原始数据或非blittable数据。
这是因为返回的句柄允许您获取固定对象的地址。为了从非托管代码中使用此地址,必须将对象从托管内存编织到非托管内存。
此代码不会引发异常:
MyClass o = new MyClass();
GCHandle h = GCHandle.Alloc(o, GCHandleType.Normal);
因为您无法使用返回的句柄来获取地址。
所以回答你的问题:
一种情况是托管程序集的纯C API。 API可能如下所示:
MYHANDLE h1 = MyLib_CreateComponent();
MYHANDLE h2 = MyLib_CreateComponent();
MyLib_SetX(h1, 9.81);
double y1 = MYLib_CalcY(h1);
MyLib_SetX(h2, 3.14);
double y2 = MyLib_CalcY(h2);
printf("z = %f\n", y1 + y2);
MyLib_DestroyComponent(h1);
MyLib_DestroyComponent(h2);
无法从C代码直接访问该对象。
函数MyLib_CreateComponent()的C#实现如下所示:
public static int CreateComponent()
{
MyClass instance = new MyClass();
GCHandle gch = GCHandle.Alloc(instance, GCHandleType.Normal);
IntPtr ip = GCHandle.ToIntPtr(h);
h = ip.ToInt32();
return h;
}
在托管代码中,我会创建一个方法来使用句柄获取对象:
static MyClass GetObjectFromHandle(int hComp)
{
IntPtr ip = new IntPtr(hComp);
GCHandle h = GCHandle.FromIntPtr(ip);
MyClass comp = h.Target as MyClass;
return comp;
}
答案 2 :(得分:2)
根据经验,当您希望从本机代码(未访问)中传递指针时,可以使用Normal
。并且,当您期望从本机代码访问指针时,请使用Pinned
。
这是因为当您将Normal
对象传递给本机代码时,您只能将IntPtr
传递给GCHandle
(通过GCHandle.ToIntPt
进行检索)。而且只能通过GCHandle.FromIntPt
将其重新恢复到托管代码中。
可以在以下位置找到有关其工作原理的很好的解释:https://blogs.msdn.microsoft.com/jmstall/2006/10/09/gchandle-tointptr-vs-gchandle-addrofpinnedobject/