IntPtr, 64 bit Porting and Fun
Guys I was busy in some HLD for past two weeks or so. I'm back now into my favourite blogspace - DNJ.
Yesterday when our 64 bit porting team is working for the immediate 64 bit release of our product, they came to me for suggestion on a topic which I think worth discussing. While they are happily ported unmanaged C++ code, they were facing some difficulties with porting managed code(which should be easier! right?). In unmanaged C++ world they handled pointer disparity between the platforms with smart #ifdefs and #define macros. But in managed C# layer we do have some unsafe pointer code that interoperate to the unmanaged C++ code. Situation become tricky. As there is no #define macros in C# world, they were unable to write ubiquitous code that would work on either platform.
In one particular situation we have a long native 64 bit pointer allocated in unmanaged layer and C# unsafe code would like to interoperate with it in following way.
___ ___ ___ ___ ___ ___ ______
x ----------> |___|___|___|___|___|___|___|___|
In 32 bit platform we wanted to access first 4 bytes(32 bit pointer size) of x
... = *((int*)x)
In 64 bit platform ,analogously, we wanted to access first 8 bytes(64 bit pointer size) of x
... = *((__int64*)x)
And in both cases we also used to use integer pointer arithmatic by adding integer offset values to (int*)x or (__int64*)x (which hasn't been shown). No problem as such here. The problem relates to merging these two set of lines to one. I immediately scream "use UIntPtr...". But just how? Well we need to construct a UIntPtr out of the long value - x. In looking through Reflector the specified ctor appears like below
public unsafe UIntPtr(UInt64 value)
{
this.m_value = (void*) ((uint) value);
}
But mine was a 32 bit CLR. What I guess that in 64 bit this ctor will not truncate the long value. It should be something like below
public unsafe UIntPtr(UInt64 value)
{
this.m_value = (void*) value;
}
A look into Rotor confirmed my guess. One can check IntPtr.cs file here
public unsafe UIntPtr(UInt64 value)
00051 {
00052 #if WIN32
00053 m_value = (void *)checked((uint)value);
00054 #else
00055 m_value = (void *)value;
00056 #endif
00057 }
The consequence is significant. This means m_value holds platform-specific integer pointer and so does the return value of ToPointer() which just exposes the m_value.
public unsafe void* ToPointer()
{
return this.m_value;
}
So the code should be something like below
UIntPtr uiptr = new UIntPtr(x); // Invoke the specified ctor
... = *(uiptr.ToPointer());
But the explicit construction of the UIntPtr doesn't look elegant. Then I looked at the following casting operator of UIntPtr which takes a long(64 bit) value and convert it to an UIntPtr -
public static explicit operator UIntPtr(UInt64 value)
{
return new UIntPtr(value);
}
This conversion operator internally invokes the same ctor we are interested with. So now the revised code looks like
... = *(((UIntPtr)x).ToPointer());
64 bit porting of managed application sometimes can create little surprises. When managed code heavily deals with P/Invoke, COM Interop and unsafe(c#) code porting becomes non-trivial. If interested you can look at this official MSDN 32-to-64 bit migration guide for managed code http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/64migrate.asp.
By the way I'm reading two wonderful books - both are theoretically non-windows books - "Art of Unix Programming" and "Innovation Happens Elsewhere". Do lots of programming4fun.