Marshaling structs with string pointers

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?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s