PInvoke with 32 and 64 bit machines

One of the big difficulties still remaining in .NET 2.0 is the interoping with native API's which takes in types that have different sizes on 32 and 64 bit machines. For most API's this is not a problem. But for many, including most of the memory API's for example, the signature is technically different from 32 to 64 bit with respect to parameter size.

The problem occurs if any of the API’s parameters have a different size on 32 and 64 bit.  IMHO, most common Win32 API parameters are composed of types that do not have different sizes on 64 bit (DWORD, UINT, LONG, etc …).  The biggest culprit for me has been size_t.

Unfortunately there is no predefined solution for these types in .NET 2.0.  So one has to be devised.  The biggest issue is that you typically run the same .NET binary or 32 and 64 bit.  So we need a type whose size is determined at runtime based on hints from the platform.  Defining a type like this just isn’t possible with .NET.  The best recourse is to define a fixed size type and a custom marshaler that will make the runtime size decision. 

As a proof of concept I added a type SizeT to my interop library and a custom marshaling class to do the dirty work for 32 and 64 bit platforms.  Here are the definitions. 

 public sealed class SizeT
 {
  private ulong m_value;

  public ulong Value
  {
   get { return m_value; }
   set { m_value = value; }
  }

  public SizeT(ulong val)
  {
   m_value = val;
  }

  [SuppressMessage("Microsoft.Usage", "CA2225")]
  public static implicit operator SizeT(ulong value)
  {
   return new SizeT(value);
  }

  [SuppressMessage("Microsoft.Usage", "CA2225")]
  public static implicit operator ulong(SizeT value)
  {
   return value.Value;
  }
 }

 public sealed class SizeTMarshaler : ICustomMarshaler
 {
  [SuppressMessage("Microsoft.Usage", "CA1801", Justification="data parameter is a hidden requirement of the API")]
  public static ICustomMarshaler GetInstance(string data)
  {
   return new SizeTMarshaler();
  }

  #region ICustomMarshaler Members

  public void CleanUpManagedData(object ManagedObj)
  {
   // Nothing to do
  }

  public void CleanUpNativeData(IntPtr pNativeData)
  {
   Marshal.FreeCoTaskMem(pNativeData);
  }

  public int GetNativeDataSize()
  {
   return IntPtr.Size;
  }

  public IntPtr MarshalManagedToNative(object ManagedObj)
  {
   SizeT value = (SizeT)ManagedObj;

   checked {

   IntPtr ptr = Marshal.AllocCoTaskMem(IntPtr.Size);
   if (IntPtr.Size == 4)
   {
    Marshal.StructureToPtr((uint)value.Value, ptr, true);
   }
   else if (IntPtr.Size == 8)
   {
    Marshal.StructureToPtr(value.Value, ptr, true);
   }
   else
   {
    throw new ArgumentException("Invalid Pointer Size");
   }

   }

   return ptr;
  }

  public object MarshalNativeToManaged(IntPtr pNativeData)
  {
   if (IntPtr.Size == 4)
   {
    uint val = (uint)Marshal.PtrToStructure(pNativeData, typeof(uint));
    return new SizeT(val);
   }
   else if (IntPtr.Size == 8)
   {
    ulong val = (ulong)Marshal.PtrToStructure(pNativeData, typeof(ulong));
    return new SizeT(val);
   }
   else
   {
    throw new ArgumentException("Invalid Pointer Size");
   }
  }

  #endregion
 }

 

There are two ways to determine if your interop API's are affected by the move to a 64 bit platform.

  1. Brute Force. Find all of the native types that you use and determine if there are different on 64 bit
  2. Turn on FxCop Portability rules and it will catch the majority of your errors.