views:

649

answers:

4

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?

+1  A: 

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.

David Archer
A: 

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?

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.

Christopher
If they are indeed on the stack, they cannot be moved by the garbage collector. But value types can also exist as members of objects on the heap, which will move around.
Ben Voigt
A: 

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.

The first sentence of your question is wrong and really confuses the entire discussion. Types are never stored on the stack (`System::Type` is a reference type). Variables of value type are stored on the stack if and only if they are ordinary local variables or function parameters (members of reference types and captured local variables are stored on the heap, so are array contents). So `pin_ptr` is useful in the case where the instance of the value type is inside a heap object.
Ben Voigt
+1  A: 

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,

If I pass &b to an unmanaged function without first pinning it, may the stack become corrupted?

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.

dapi