在C ++ CLR值类型上需要pin_ptr,为什么?

时间:2009-07-09 16:30:03

标签: c++ clr managed-c++

由于.NET值类型(托管C ++结构)存储在堆栈中,为什么它是(或者实际上是)为了将指针传递给非托管函数而需要pin_ptr它们?

EG。 BYTE b [100];

如果我在没有先固定它的情况下将& b传递给非托管功能,堆栈是否会损坏?

CLR堆栈是否会以与GC堆相同的方式进行更改?我被认为CLR堆栈使用异常优化,例如使用处理器寄存器,这使得它不适合用作非托管函数的缓冲区。关于在堆栈上固定值类型的规则似乎不清楚。

我注意到以这种方式将缓冲区数组发送到内核NTDLL函数NtfsControlFile时似乎有些损坏。固定值类型可以解决问题。但绝不会进行API调用。

因此,如果没有首先固定它们,将堆栈上的任何值类型的任何指针传递给任何非托管函数,是否真的不安全?

4 个答案:

答案 0 :(得分:2)

你是正确的,BYTE b[100];是在本机堆栈上创建的,因此不受托管堆移动等的影响。但是我相信你的问题是一个简单的C ++错误。

你说,

  

如果我将& b传递给非托管函数   没有先固定它,可能是   堆栈变坏了?

您不应该在数组名称(b)上使用address-of运算符(&),因为数组名称本身已经是数组开头的地址。使用&b将不起作用,结果行为将取决于多个因素(例如,编译器及其设置)。

简单地说,你应该只是传递数组名称(b)而不是在调用函数时使用数组名称(& b)上的address-of运算符。

顺便说一句,我相信你通过询问是否可以将堆栈上的托管值类型传递给本机函数而不先固定它来混淆问题,因为你给出的例子是传递一个非托管的堆栈 - 基于本机数组类型,与托管值类型无关。

答案 1 :(得分:1)

是的,内存管理能够切换地址,它只是更新自己对它们的内部引用。一旦您在托管图层下方潜水,您必须确保您正在使用的指针不会被移动到另一个位置。 pin_ptr的使用告诉内存管理器单独留下这段内存。

答案 2 :(得分:0)

  

因此,如果没有首先固定它们,将堆栈上的任何值类型的任何指针传递给任何非托管函数,是否真的不安全?

这是由于GC删除​​/移动它们与您的方法异步。

有关CLR GC如何工作的说明,请参阅moving GC

答案 3 :(得分:0)

据我所知,这一切都是指GC堆上的对象。

关键是我指的是STACK内存。

我发布了一个示例,似乎表明堆栈内存在API调用期间被损坏,将堆栈内存作为缓冲区传递: http://social.msdn.microsoft.com/Forums/en-US/clr/thread/3779c1ee-90b8-4a6a-9b14-f48d709cb27c

如果需要固定堆栈内存,那么这似乎打破了“It Just Works”的想法。在非托管C ++中,我们可以声明一个堆栈缓冲区,然后将指针传递给API函数。但是,如果转向托管代码需要固定,那么它似乎会从根本上破坏“It Just Works”。

令人困惑的是,针对pin_ptr的MSDN文档似乎只是为了防止对象移动,但是也可能是Pin值似乎在堆栈上的值,并且不应该移动。

我特别提出了在托管代码或非托管代码中是否以相同方式处理堆栈内存的问题。当MSIL调试时,我发现无法查看堆栈,并且没有堆栈查看器工具。我听说过,但不确定,MSIL中没有“真正的”堆栈,而虚拟机CLR可以自由优化,例如使用免费的处理器寄存器而不是实际的内存。目前还不清楚这是否属实,以及它是否适用于参数传递中的堆栈,或者是否应用于局部变量内存中。

上述示例项目中的一个奇怪的影响是腐败对象上的pin_ptr似乎可以解决问题。但是对象在STACK上,不需要固定。可能是/ CLR将pin_ptr解释为不仅“不移动此对象”而且“将此区域保留为真正的内存并且不会尝试对其进行寄存器优化”,这会导致它在引脚的持续时间内保持纯净状态?

我特别想知道/ CLR是否足够聪明,可以避免在调用API时避免优化其方法内堆栈内存,但在上面的例子中可能不会给我相同的优势,因为直接加载NTDLL以及将函数声明为typedef的方式。

我考虑过将编组属性添加到函数typedef但似乎无法这样做。我注意到WinAPI defs上没有MarshallAs属性。

我已经设法在NTDLL调用之前使用__debugbreak()进入上面的项目但是这只给了我一个托管调试模式,它似乎无法进入本机代码。我不能写“asm int 3”因为x64不支持它。但是,我可以看到,错误的值NumberOfPairs正在寄存器指向的存储器位置传递,而不是寄存器本身。