Monday, February 21, 2005

I'm working on a fairly straightforward wrapper for a customer and it occurred to me that there are a distinct lack of simple working examples of the “more difficult” marshaling techniques in the CF. A classic example is a native struct that contains a pointer to a string. On the desktop it's simple (and in CF 2.0 it's pretty easy too), but in CF 1.0, it's not straightforward.

Personally I don't mind the code required to do the marshaling - in fact I think everyone using marshaling should be required to understand these fundamentals. While that's never going to happen, some people still are interested, or their project may necessitate being able to do it, and of course having a sample never hurts.

So consider the following very simple struct. A simgle 4-byte integer followed by a pointer to a string:

typedef struct
{
 INT IntMember,
 LPSTR StringMember
} MYSTRUCT, *PMYSTRUCT;

What appears to be rather simple in C - just a mere 5 lines of code - expands to the following not-so-small 80-line code base in C#:

public class EmbeddedStringPtrStruct
{
 // offsets used internally
 private const int intMemberOffset = 0;
 private const int stringMemberOffset = 4;
 // this is the actual struct data to be passed to native methods
 protected byte[] m_bytes = new byte[8];
 // private var to hold string data pointer
 private IntPtr m_pStringMember = IntPtr.Zero;
 // default public ctor
 public EmbeddedStringPtrStruct()
 {
 }
 // dtor - call Dispose if the consumer didn't
 ~EmbeddedStringPtrStruct()
 {
  this.Dispose();
 }
 // operator to get a byte array used for marshaling
 // this allows you to pass the EmbeddedStringPtrStruct directly
 public static implicit operator byte[](EmbeddedStringPtrStruct esps)
 {
  return esps.m_bytes;
 }
 // accessor for INT member
 public int IntMember
 {
  get
  {
   return BitConverter.ToInt32(m_bytes, intMemberOffset);
  }
  set
  {
   byte[] bytes = BitConverter.GetBytes(value);
   Buffer.BlockCopy( bytes, 0, m_bytes, intMemberOffset, 4 );
  }
 }
 public string StringMember
 {
  get
  {
   IntPtr ptr = (IntPtr)BitConverter.ToInt32( m_bytes, stringMemberOffset );
   return MarshalEx.PtrToStringUni(ptr);     
  }
  set
  {
   // first see if we already have one allocated
   if(! m_pStringMember.Equals(IntPtr.Zero))
   {
    MarshalEx.FreeHGlobal(m_pStringMember);
   }
   // alloc storage for the string so we can pass the ptr
   m_pStringMember = MarshalEx.StringToHGlobalUni(value);
   // convert to an Int32 and get the bytes
   byte[] bytes = BitConverter.GetBytes( m_pStringMember.ToInt32() );
   // stuff it into the global
   Buffer.BlockCopy( bytes, 0, m_bytes, stringMemberOffset, 4 );    
  }
 }
 public void Dispose()
 {
  // clean up native memory allocations
  if(! m_pStringMember.Equals(IntPtr.Zero))
  {
   MarshalEx.FreeHGlobal(m_pStringMember);
  }
 }
}

See, not so bad, is it?

2/21/2005 10:14:39 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  |