2

I have a legacy application written in Delphi 2007 which generates byte arrays like this:

command_data = packed record
    direction     : word;
    name          : array [0..9] of char;
end;

command_header = packed record
    length      : word;
    data1       : word;
    data2       : cardinal;
end;

command_data_container = packed record
    data          : command_data;
    containerId   : word;
end;

function Generate(name: string)boolean;
var
  header  : command_header;
  container : command_data_container;
  charArrayName: array [0..9] of char;

begin
  charArrayName = array [0..9] of char;

  for I := 0 to Length(name) - 1 do begin
    charArrayName[i] := name[i+1];
  end;
  charArrayName[i+1] := #0;

  header.length := sizeof(header) + sizeof(container);
  header.data1 := 0;
  header.data2 := 1;

  container.data.direction := 1;
  container.data.name      := charArrayName;
  container.containerId    := 1;

  stream := TMemoryStream.Create;
  stream.WriteBuffer(header, SizeOf(header));
  stream.WriteBuffer(container, SizeOf(container));
  //...
 end;

I need to rewrite this part in C#. I got this so far:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct command_data
{
    public ushort direction;
    public fixed char name[10];
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct command_header
{
    public ushort length;
    public ushort data1;
    public ulong data2;
}    

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct command_data_container
{
    public command_data data;
    public ushort containerId;
} 

 public bool Generate(string name)
 {
    name = name + Char.MinValue; // Add null terminator.
    char[] charArrayName = agentId.ToCharArray();

    unsafe
    {
        command_header header;
        command_data_container command;

        header.data1 = 0;
        header.data2 = 1;
        header.length = (ushort)(sizeof(command_header) + sizeof(command_data_container));


        command.data.direction = 1;
        *command.data.name = charArrayName[0];

        for (int i = 1; i < charArrayName.Length; i++)
        {
            *(command.data.name + i) = charArrayName[i];
        }
        command.containerId = 1;

        var headerBytes = StructToBytes<command_header>(header);
        var commandBytes = StructToBytes<command_data_container>(command);

        byte[] combined = new byte[headerBytes.Length + commandBytes.Length];
        Buffer.BlockCopy(headerBytes, 0, combined, 0, headerBytes.Length);
        Buffer.BlockCopy(commandBytes, 0, combined, headerBytes.Length, commandBytes.Length);

        //combined should have the same data as the stream in the delphi code

    }
 }


public static byte[] StructToBytes<T>(T structure) where T : struct
{
    int size = Marshal.SizeOf(structure);
    byte[] rawData = new byte[size];
    GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
    try
    {
        IntPtr rawDataPtr = handle.AddrOfPinnedObject();
        Marshal.StructureToPtr(structure, rawDataPtr, false);
    }
    finally
    {
        handle.Free();
    }
    return rawData;
}

I tried several methods to convert the struct to a byte array, but none of them reproduced the same result as the Delphi code.

The StructToBytes method is borrowed from here: C# performance - Using unsafe pointers instead of IntPtr and Marshal

I also tried some others marshalling methods from SO, but nothing worked. What did I do wrong?

9
  • You know that Delphi 2007 Char is AnsiChar and in C# it is Unicode? Commented Nov 6, 2017 at 23:10
  • Char in c# are two bytes while in c++ they are one byte. So in c# use : new byte[10]. The array also must be terminated with a '\0' (0x00). I usually use : Encoding.UTF8.GetBytes("JohnSmith\0"); Commented Nov 7, 2017 at 1:01
  • 1
    If you must rewrite this in C#, then why do you care about interop and marshalling? Just generate the same number of bytes. Can't be too hard. Commented Nov 7, 2017 at 7:31
  • I agree with @Rudy here. It's simpler to write directly to byte streams. Obviously the big problem here is the size issue around char as already identified. I'd also point out that your Delphi code is ripe for buffer overrun. Commented Nov 7, 2017 at 8:19
  • @jdweng Encoding.Default is appropriate here to match the Delphi code. I see no evidence that the input string is UTF-8 encoded. ANSI is the norm for Delphi 2007. Commented Nov 7, 2017 at 9:29

1 Answer 1

2
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
struct command_data
{
    public ushort direction;
    [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 10)]
    public string name;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Some explanation would be nice here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.