mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							94 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
	
	
							94 lines
						
					
					
						
							6.0 KiB
						
					
					
				C# casts to/from IntPtr and UIntPtr don't behave like one would expect from the normal C# primitive types. | 
						|
For example, they don't fully respect the checked/unchecked context. | 
						|
 | 
						|
First, let's consider what methods we have available for converting between (U)IntPtr and normal C# types. | 
						|
 | 
						|
Primitives for constructing IntPtr/UIntPtr: | 
						|
 * new IntPtr(int)    equivalent to: conv i4->i <sign extend> | 
						|
 * new IntPtr(long)   equivalent to: conv.ovf i8->i | 
						|
 * new IntPtr(void*)  equivalent to: nop | 
						|
 * new UIntPtr(uint)  equivalent to: conv u4->u <zero extend> | 
						|
 * new UIntPtr(ulong) equivalent to: conv.ovf u8->u | 
						|
 * new UIntPtr(void*) equivalent to: nop | 
						|
 | 
						|
Primitives for getting the value back out: | 
						|
 * IntPtr.ToInt32()    equivalent to: conv.ovf i->i4 | 
						|
 * IntPtr.ToInt64()    equivalent to: conv i->i8 <sign extend> | 
						|
 * IntPtr.ToPointer()  equivalent to: nop | 
						|
 * UIntPtr.ToUInt32()  equivalent to: conv.ovf u->u4 | 
						|
 * UIntPtr.ToUInt64()  equivalent to: conv u->u8 <zero extend> | 
						|
 * UIntPtr.ToPointer() equivalent to: nop | 
						|
 | 
						|
The (U)IntPtr.op_Explicit implementations are equivalent to the corresponding primitives. | 
						|
(void*) is a useful type because all (U)IntPtr<->void* conversions are no-ops. | 
						|
C# pointer types act like a normal C# unsigned integer type (of 'native int' size), so we can | 
						|
use `void*` whenever the target type is unsigned or the sign does not matter (overflow checking disabled). | 
						|
 | 
						|
Next, we'll consider what the C# compiler does when casting between integer types and IntPtr. | 
						|
I tried all these conversions in both checked and unchecked mode, and the C# compiler was | 
						|
always generating the same code in both modes! | 
						|
 | 
						|
OK = cast behavior is as if IntPtr was a built-in type and the context does not matter: | 
						|
	* sign/zero extension depending on source type | 
						|
	* never throws OverflowException and never is supposed to | 
						|
CC = cast behavior is as if IntPtr was a built-in type and we are in a checked context: | 
						|
	* sign/zero extension depending on source type | 
						|
	* performs correct overflowing checking as in a direct cast to native (u)int | 
						|
from -> to = C# cast | 
						|
generated opcode sequence = what csc.exe produces for that cast | 
						|
 | 
						|
     from ->    to  : generated opcode sequence            overall effect equivalent to | 
						|
OK short  ->  IntPtr: call op_Explicit(int32)              conv i2->i <sign extend> | 
						|
OK ushort ->  IntPtr: call op_Explicit(int32)              conv u2->u <zero extend> | 
						|
                      Sign extension in op_Explicit does not matter because sign bit is always 0 at that point. | 
						|
OK int    ->  IntPtr: call op_Explicit(int32)              conv i4->i <sign extend> | 
						|
CC uint   ->  IntPtr: conv.u8 + call op_Explicit(int64)    conv.ovf u4->i <zero extend> | 
						|
CC long   ->  IntPtr: call op_Explicit(int64)              conv.ovf i8->i | 
						|
   ulong  ->  IntPtr: call op_Explicit(int64)              conv.ovf i8->i | 
						|
   short  -> UIntPtr: conv.i8 + call op_Explicit(uint64)   32-bit: conv.ovf i2->u <sign extend>;  64-bit: conv i2->i <sign extend> | 
						|
                                                              OverflowException for negative input values only on 32-bit! | 
						|
OK ushort -> UIntPtr: call op_Explicit(uint32)             conv u2->u <zero extend> | 
						|
   int    -> UIntPtr: conv.i8 + call op_Explicit(uint64)   32-bit: conv.ovf i4->u <sign extend>;  64-bit: conv i4->i <sign extend> | 
						|
                                                              OverflowException for negative input values only on 32-bit! | 
						|
OK uint   -> UIntPtr: call op_Explicit(uint32)             conv u4->u <zero extend> | 
						|
   long   -> UIntPtr: call op_Explicit(uint64)             conv.ovf u8->u | 
						|
CC ulong  -> UIntPtr: call op_Explicit(uint64)             conv.ovf u8->u | 
						|
 | 
						|
If an unchecked conversion is desired and the desired entry is not marked 'OK', | 
						|
we work around the problem by casting sourceType->void*->(U)IntPtr. | 
						|
 | 
						|
If a checked conversion is desired and the desired entry is not marked 'OK' or 'CC', we have to find a replacement. | 
						|
  signed type -> UIntPtr: (UIntPtr)(void*)value | 
						|
  ulong -> IntPtr:        (IntPtr)(long)value | 
						|
 | 
						|
Continuing the conversion table for the other direction, (UIntPtr) to primitive types: | 
						|
     from ->    to  : generated opcode sequence            overall effect equivalent to | 
						|
   IntPtr -> short:  call int32 op_Explicit + conv.i2      conv.ovf i->i4; conv i4->i2 | 
						|
   IntPtr -> ushort: call int32 op_Explicit + conv.u2      conv.ovf i->i4; conv i4->u2 | 
						|
CC IntPtr -> int:    call int32 op_Explicit                conv.ovf i->i4 | 
						|
   IntPtr -> uint:   call int32 op_Explicit                conv.ovf i->i4 | 
						|
OK IntPtr -> long:   call int64 op_Explicit                conv i->i8 <sign extend> | 
						|
   IntPtr -> ulong:  call int64 op_Explicit                conv i->i8 <sign extend> | 
						|
   UIntPtr -> short:  call uint32 op_Explicit + conv.i2    conv.ovf u->u4; conv u4->i2 | 
						|
   UIntPtr -> ushort: call uint32 op_Explicit + conv.u2    conv.ovf u->u4; conv u4->u2 | 
						|
   UIntPtr -> int:    call uint32 op_Explicit              conv.ovf u->u4 | 
						|
CC UIntPtr -> uint:   call uint32 op_Explicit              conv.ovf u->u4 | 
						|
   UIntPtr -> long:   call uint64 op_Explicit              conv u->u8 <zero extend> | 
						|
OK UIntPtr -> ulong:  call uint64 op_Explicit              conv u->u8 <zero extend> | 
						|
 | 
						|
If an unchecked conversion is desired and the desired entry is not marked 'OK', | 
						|
we work around the problem by casting (U)IntPtr->(u)long->targetType. | 
						|
(`void*` would also work instead of `ulong`/`long`, but let's avoid unsafe code where possible) | 
						|
 | 
						|
If a checked conversion is desired and the desired entry is not marked 'OK' or 'CC', | 
						|
we also have to work around the problem, and this again works by casting via (u)long: (U)IntPtr->(u)long->targetType | 
						|
(note that `void*` is not always a valid alternative in this case) | 
						|
 | 
						|
Finally, conversions between IntPtr and (U)IntPtr, or IntPtr and `void*` need special consideration: | 
						|
  * C# does not allow directly casting IntPtr <-> UIntPtr | 
						|
  * Casting via `void*` works but is always unchecked. | 
						|
  * These should work for checked conversions: | 
						|
     IntPtr -> UIntPtr: cast IntPtr->long->ulong->UIntPtr | 
						|
     IntPtr -> void*:   cast IntPtr->long->void* | 
						|
     UIntPtr -> IntPtr: cast UIntPtr->ulong->long->IntPtr | 
						|
     void*  -> IntPtr:  cast void*->long->IntPtr
 | 
						|
 |