需要在 C++ 上使用 pin_ptr CLR 值类型,为什么?
由于 .NET 值类型(托管 C++ 结构)存储在堆栈中,为什么需要(或者实际上)pin_ptr 它们以便将指针传递给非托管函数?
例如。 字节b[100];
如果我将 &b 传递给非托管函数而不先固定它,堆栈可能会损坏吗?
CLR 堆栈是否会像 GC 堆一样发生变化? 我相信 CLR 堆栈使用了不寻常的优化,例如使用处理器寄存器,这使得它不适合用作非托管函数的缓冲区。 关于在堆栈上固定值类型的规则似乎不清楚。
我注意到以这种方式将缓冲区数组发送到内核 NTDLL 函数 NtfsControlFile 时似乎出现了一些损坏。 固定值类型可以解决问题。 但绝不是 API 调用。
因此,在不首先固定它们的情况下,将指向堆栈上任何值类型的任何指针传递给任何非托管函数从根本上来说不是不安全的吗?
Since .NET Value types (managed C++ structs) are stored on the Stack why is it (or, alternatively is it actually) necessary to pin_ptr them in order to pass a pointer to an unmanaged function?
Eg. BYTE b[100];
If I pass &b to an unmanaged function without first pinning it, may the stack become corrupted?
Is the CLR stack subject to change in the same way as the GC Heap? I am led to believe that the CLR Stack uses unusual optimizations such as using processor registers which makes it unsuitable for use as buffer to unmanaged functions. The rules regarding pinning value types on the stack seem to be unclear.
I have noticed what seems to be some corruption when sending buffer arrays in this way to the kernel NTDLL function NtfsControlFile. Pinning the value type solves the problem. But never to an API call.
Is it not therefore, fundamentally unsafe to pass any pointers to any value types on the stack to any unmanaged functions, without first pinning them?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您是正确的,
BYTE b[100];
是在本机堆栈上创建的,因此不受托管堆移动等的影响。但是我相信您的问题是一个简单的 C++ 错误。你说,
您不应该在数组名称 (b) 上使用取址运算符 (&),因为数组名称本身已经是数组开头的地址。 使用
&b
将不起作用,并且所产生的行为将取决于多个因素(例如,编译器及其设置)。简而言之,在调用函数时,您应该只传递数组名称 (b),而不是在数组名称 (&b) 上使用寻址运算符。
顺便说一句,我相信您通过询问是否可以将堆栈上的托管值类型传递给本机函数而无需首先固定它来混淆问题,因为您给出的示例是传递非托管的、基于堆栈的本机数组type,与托管值类型无关。
You are correct that
BYTE b[100];
is created on the native stack and therefore is not subject to managed heap moving, etc. However I believe your problem is a simple C++ mistake.You say,
You should not be using the address-of operator (&) on the array name (b) since the array name by itself is already the address of the start of the array. Using
&b
will not work, and the resulting behavior will depend upon multiple factors (e.g., the compiler and its settings).Simply stated, you should just be passing the array name (b) instead of using the address-of operator on the array name (&b) when calling the function.
By the way, I believe you are mixing up issues by asking whether one may pass a managed value type on the stack to a native function without first pinning it, since the example you give is that of passing an unmanaged, stack-based native array type, which has nothing to do with managed value types.
是的,内存管理能够切换地址,它只是更新自己对它们的内部引用。 一旦您潜入管理层,您就必须确保您正在使用的指针不会被移动到另一个位置。 pin_ptr 的使用告诉内存管理器不要管这块内存。
Yes, the memory management is able to switch addresses and it just updates its own internal references to them. As soon as you dive below the managed layer, you have to ensure the pointer you are working with is safe from being moved to another location. The use of pin_ptr tells the memory manager to leave this piece of memory alone.
是的。
这是由于 GC 异步删除/移动它们到您的方法。
有关 CLR GC 如何工作的说明,请参阅移动 GC。
Yes.
This is due to the GC deleting/moving them asynchronously to your method.
See moving GC for a description of how the CLR GC works.
据我所知,这一切都是指 GC 堆上的对象。
重点是我特指的是堆栈内存。
我发布了一个示例,似乎表明在将堆栈内存作为缓冲区传递的 API 调用期间堆栈内存被损坏:
http://social. msdn.microsoft.com/Forums/en-US/clr/thread/3779c1ee-90b8-4a6a-9b14-f48d709cb27c
如果堆栈内存需要固定,那么这似乎打破了“它只是工作”的想法。 在非托管 C++ 中,我们可以声明一个堆栈缓冲区,然后将指向它的指针传递给 API 函数。 然而,如果转向托管代码需要固定它,那么它似乎从根本上破坏了“它只是有效”。
令人困惑的是,pin_ptr 的 MSDN 文档似乎说它只是为了防止对象移动,但是也可以 Pin 值类型,这些类型似乎在堆栈上并且无论如何都不应该移动。
我特别提出一个问题:在托管代码或非托管代码中是否以相同的方式处理堆栈内存。 当 MSIL 调试时,我发现无法查看堆栈,并且没有可用的堆栈查看器工具。 我听说过,但不确定,MSIL 中没有“真正的”堆栈,而是虚拟机 CLR 可以自由优化,例如使用空闲处理器寄存器而不是实际内存。 目前尚不清楚这是否属实,也不清楚它是否适用于参数传递中的堆栈,或局部变量内存中的堆栈。
上述示例项目中的一个奇怪的效果是损坏对象上的 pin_ptr 似乎可以解决问题。 但是该对象位于堆栈上,不需要固定。 难道 /CLR 不仅将 pin_ptr 解释为“不要移动该对象”,而且还“将此区域保留为真正的内存,并且不要尝试对其进行寄存器优化”,这将导致它在 pin 的持续时间内保持纯净状态?
我特别想知道 /CLR 是否足够聪明,可以在调用 API 期间避免优化其方法内堆栈内存,但由于直接加载NTDLL 以及将函数声明为 typedef 的方式。
我考虑过向函数 typedef 添加编组属性,但似乎无法这样做。 我注意到 WinAPI def 上没有 MarshallAs 属性。
我已经成功地在 NTDLL 调用之前使用 __debugbreak() 闯入了上面的项目,但这只给了我一个托管调试模式,它似乎无法进入本机代码。 我不能写“asm int 3”,因为 x64 不支持它。 然而,我可以看到错误值 NumberOfPairs 是在寄存器指向的内存位置传递的,而不是作为寄存器本身传递的。
As far as I can tell this all refers to objects on the GC Heap.
The point is I am referring to specifically STACK memory.
I have posted an example which seems to suggest that stack memory is being corrupted during an API call passing stack memory as a buffer:
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/3779c1ee-90b8-4a6a-9b14-f48d709cb27c
If stack memory needs to be pinned, then this seems to break the idea of "It Just Works". In unmanaged C++ we can declare a stack buffer and then pass a pointer to it to an API function. However if moving to managed code requires that to be pinned, it would seem to fundamentally undermine "It Just Works".
It is confusing how the MSDN docs for pin_ptr seem to say it is only to prevent objects moving however it is also possible to Pin value types which would seem to be on the stack and should not move anyway.
I specifically raise the question of whether the stack memory is treated the same way in managed or unmanaged code. When MSIL debugging I have found it impossible to view the stack and there is no stack viewer tool for that. I have heard, but am not sure, that there is no "real" stack in MSIL and instead the virtual machine CLR is free to optimize, for example using free processor registers instead of actual memory. It is unclear whether this is true, and, whether it would apply to stack as in parameter passing, or stack as in local variable memory.
An odd effect in the above sample project is that pin_ptr on the corrupting object seems to cure the problem. however the object is on the STACK and should not need pinning. Could it be that the /CLR interprets pin_ptr as not only "do not move this object" but also "leave this area as true memory and do not attempt register optimizations upon it" which would cause it to remain pure for the duration of the pin?
I would specifically like to know if the /CLR is clever enough to say avoid optimizations of its in-method stack memory during a call to an API, but would possibly not give me the same grace in the above example due to the direct loading of NTDLL and the way the function is declared as a typedef.
I have considered adding Marshalling attributes to the function typedef but seem unable to do so. I note there are no MarshallAs attributes on WinAPI defs.
I have managed to break into the project above using __debugbreak() immediately before the NTDLL call however this only gives me a managed debug mode which seems unable to step into native code. I cannot write "asm int 3" because x64 does not support it. I can see, however, that the errant value NumberOfPairs is being passed at a memory location pointed to by a register, not as a register itself.