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
|
|
|