4

I am packing bytes into a struct, and some of them correspond to a Unicode string. The following works fine for an ASCII string:

[StructLayout(LayoutKind.Sequential)]
private struct PacketBytes
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string MyString;
}

I assumed that I could do

[StructLayout(LayoutKind.Sequential)]
private struct PacketBytes
{
    [MarshalAs(UnmanagedType.LPWStr, SizeConst = 32)]
    public string MyString;
}

to make it Unicode, but that didn't work (the field value was empty and the other fields had incorrect values, indicating that the byte unpacking was messed up). (Since this field is part of a struct with other fields, which I've omitted for clarity, I can't simply change the CharSet of the containing struct.)

Any idea what I'm doing wrong?

Here is the input (64 bytes, little endian):

31:00:31:00:32:00:33:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00

The output should be the Unicode string "1123".

Pat
  • 16,515
  • 15
  • 95
  • 114
  • 1
    What do you mean by "didn't work"? What exactly failed? By the way, relying on "tstring" is a design problem - it relies on legacy _UNICODE define. I also believe using UTF-16 is wrong, but that's another story. – Pavel Radzivilovsky Jun 01 '10 at 17:06
  • There is no `_UNICODE` macro in C#, and the `_UNICODE` and `UNICODE` macros in C/C++ code aren't obsolete. `TStr` simply means to use the encoding specified by the `CharSet` parameter. – Philipp Jun 01 '10 at 17:57

2 Answers2

2

I would do this by declaring a nested structure for the string type. The "inner" structure can declare its CharSet. This is similar to the solution on my blog: http://nitoprograms.blogspot.com/2010/02/interop-multidimensional-arrays-of.html

e.g.:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct StringSizeConst32AsString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    private string Value;

    public static implicit operator string(StringSizeConst32AsString source)
    {
        return source.Value;
    }

    public static implicit operator StringSizeConst32AsString(string source)
    {
        // Note that longer strings would be silently truncated
        //  if we didn't explicitly check this.
        if (source.Length >= 32)
            throw new Exception("String too large for field: " + source);

        return new StringSizeConst32AsString { Value = source };
    }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

Credit to @StephenCleary. My full solution is this:

[StructLayout(LayoutKind.Sequential)]
private struct PacketBytes
{
    [MarshalAs(UnmanagedType.Struct)]
    public UnicodeString MyString;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
private struct UnicodeString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Value;

    public static implicit operator string(UnicodeString value)
    {
        return value.Value;
    }
}
Pat
  • 16,515
  • 15
  • 95
  • 114
  • Again, in case anyone is wondering why I didn't just put the CharSet on the top struct, it my actual code there are other fields in that struct that need to use the ASCII CharSet. This solution lets you do both. – Pat Jun 01 '10 at 17:43