Simple Guy
Simple Guy

Reputation: 608

Marshalling a C++ Encoded String to C#

I am trying to read in a structure that was written to a file by a C++ program (I don't have the source code). I have been trying to read this structure into C# and marshal it so far without success.

The structure is basically a set of strings of fixed length, two-bytes per character. In C++, they can be declared as TCHAR[8].

The data on disk looks like as follows:

Data on disk

I have tried the following C# code that I know can successfully read in the values as a string:

public void ReadTwoStringsOfFixedLength()
{ 
     string field1 = string.Empty;
     string field2 = string.Empty;
     FileReadString(handle, out field1, 16);
     FileReadString(handle, out field2, 16);
}

public static void FileReadString(BinaryReader reader, out string outVal, int length)
{
    var mem = new MemoryStream();
    outVal = string.Empty;

    byte b = 0;
    for (int i = 0; i < length; i++)
    {
        b = reader.ReadByte();
        if (b != 0) mem.WriteByte(b);
    }
    outVal = Encoding.GetEncoding(1252).GetString(mem.ToArray());
}

However, what I really would like to do is use c# structs, since this data is represented as a struct in C++ (and contains other fields which I have not depicted here).

I have tried various methods of attempting to marshal this data based on answers I have read on StackOverflow, but none have yielded the result I wanted. In most cases, either the string encoding was incorrect, I ended up with a memory exception or I ended up with only the first character in each field (probably due to null-termination?)

Here is my code:

void Main()
{
   byte[] abBuffer = handle.ReadBytes(Marshal.SizeOf(typeof(MyStruct)));
   //Access data
   GCHandle pinnedPacket = GCHandle.Alloc(abBuffer, GCHandleType.Pinned);
   var atTestStruct = (MyStruct)Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(MyStruct));
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi), Serializable]
struct MyStruct
{
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
   string Field1   // Resulting value = "F"; 
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
   string Field2;  // Resulting value = "F" 
 }

Note that I have also attempted to use CharSet.Unicode, however the resulting strings are garbled.

Any help to fix the code would be much appreciated.

Upvotes: 0

Views: 749

Answers (1)

Drew Noakes
Drew Noakes

Reputation: 310907

I think you need to set CharSet = CharSet.Unicode on your StructLayout.

46 00 69 00 in ASCII/ANSI is considered a single character and a null terminator. The documentation shows that CharSet.Unicode is needed for two-byte characters, such as those you're showing.

The SizeConst value must also be the number of characters, not bytes.

Upvotes: 1

Related Questions