1

I'm converting a delphi app to C#. There are a bunch of packed records, and according to a similar question I asked a few weeks ago, it would be better to convert to classes. However, I'm told I need to convert them to structs, and I could use some help. I'm going to be using a BinaryReader to read from a file and assign values to the fields inside of the structs.

*Note, the file I'm reading from was made using Delphi and packed records.

Here's an example struct:

Delphi:

Testrec = packed record
    now: TDateTime;
    MinLat: longint;
    MinLong: longint;
    Firsttime: TDateTime;
    MinAlt: single;
    MinFirst: single;
    MinDepth: single;
    MinSpeed: single;
    MinBot: single;
    res3: single;
    res4: single;
    res5: single;
    res6: single;
    MaxLat: longint;
    MaxLong: longint;
    Lasttime: TDateTime;
    MaxAlt: single;
    MaxFirst: single;
    MaxDepth: single;
    MaxSpeed: single;
    MaxBot: single;
    res9: single;
    res10: single;
    res11: single;
    res12: single;
    DataFlags: longint;
    ReviewFlags: longint;
    res13: longint;
    FirstPost: longint;
end;

Here's my C# version:

public struct Testrec
{
    double now;
    int MinLat;
    int MinLong;
    double Firsttime;
    float MinAlt;
    float MinFirst;
    float MinDepth;
    float MinSpeed;
    float MinBot;
    float res3;
    float res4;
    float res5;
    float res6;
    int MaxLat;
    int MaxLong;
    double Lasttime;
    float MaxAlt;
    float MaxFirst;
    float MaxDepth;
    float MaxSpeed;
    float MaxBot;
    float res9;
    float res10;
    float res11;
    float res12;
    int DataFlags;
    int ReviewFlags;
    int res13;
    int FirstPost;
 }

Do I need to do a StructLayout, Size, and CharSet?

Edit: Here's the relevant delphi code regarding the reading of the binary file:

Testrec Header;
HeaderSize = 128;

RampStream:=TFileStream.Create(FilePath,fmOpenReadWrite OR fmShareExclusive );

RampStream.Read(Header,HeaderSize);
StartTime:=Header.Firsttime;
EndTime:=Header.Lasttime;

Here's how I set up my Binary Reader:

RampStream = new BinaryReader(new FileStream(RampName, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
pfinferno
  • 1,779
  • 3
  • 34
  • 62
  • 1
    Your struct is way past the recommended 16 bytes: http://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes – user9993 Mar 04 '16 at 09:09
  • The struct may be past the 16 byte limit, but that doesn't answer the question nor is it relevant. His question is mainly how to declare the struct in C# such that it contains no filler bytes, i.e.has bte alignment. FWIW, I would pass such a struct as reference, in any language. – Rudy Velthuis Mar 04 '16 at 12:21
  • 1
    @RudyVelthuis Passing as a reference means that the callee can modify it. – David Heffernan Mar 04 '16 at 12:30
  • Yes, in C# that is true. That C# can't have const parameters is a bit of a problem. But if speed is an issue, I would still pass by reference. – Rudy Velthuis Mar 04 '16 at 16:25

1 Answers1

4

You need to specify sequential layout and a pack value of 1.

[StructLayout(LayoutKind.Sequential, Pack = 1)]

Since there are not textual members, you need not specify CharSet. And you should let the compiler calculate the size of the struct. Now, having specified this you will be able to read the entire record into memory, and then blit it directly onto this C# struct. Like so:

Testrec ReadRecFromStream(Stream stream)
{
    byte[] buffer = new byte[Marshal.SizeOf(typeof(Testrec))];
    stream.Read(buffer, 0, buffer.Length);
    GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    try
    {
        return (Testrec)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Testrec));
    }
    finally
    {
        handle.Free();
    }
}

However, you said that you were going to read on member at a time and assign to the corresponding field in the C# struct. In that case there's no need to seek binary layout equivalence since you won't be making any use of that. If you are going read one member at a time then you do not need a StructLayout attribute. You don't need to declare any unused members. You can convert the Delphi date time values into the appropriate C# data types at the point of input and so on.

So, you do need to make up your mind as to whether or not you are going to seek binary layout equivalence of these structures.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you. I'm going to edit my post with the Delphi code regarding the reading from the file. It is a binary file that's being read in. – pfinferno Mar 04 '16 at 12:00
  • 1
    You can do the same thing in C# as your do in Delphi to read the file. You'd allocate some unmanaged memory. You'd read into that memory from the file. And you'd use `Marshal.PtrToStructure` to deserialize. That's going to work only if you make sure that the binary layout matches. If you use instead a BinaryReader to read field by field then the C# struct layout is not important. And I definitely think that a class is better here. Why are you being instructed to use a struct? – David Heffernan Mar 04 '16 at 12:09
  • Frankly, you need to make a decision on how you are going to handle the reading of this file. Are you going to do a binary blit as your did in Delphi. Or are you going to read field by field? – David Heffernan Mar 04 '16 at 12:10
  • I can try to convince them to use a class. I was told to use struct since there's going to be many structs being fed data from a file (in the thousands) and that it will supposedly be faster. – pfinferno Mar 04 '16 at 12:14
  • 1
    It won't be faster once you start passing them around between functions. – David Heffernan Mar 04 '16 at 12:17
  • Alright I'm just going to use classes for the time being and see how it goes. Would the reading in of the file be basically the same with a class vs struct? – pfinferno Mar 04 '16 at 12:24
  • If he is presisting the data using binary blit, a struct/record is IMO the better alternative. If he wants to read and write field by field, classes are probably the better alternative. – Rudy Velthuis Mar 04 '16 at 12:28
  • @RudyVelthuis Blitting can be done for either a struct or a class. – David Heffernan Mar 04 '16 at 12:29
  • @pfinferno: It is almost impossible to ensure binary compatbility between C# and Delphi classes, since for instance Delphi classes have some hidden fields, e.g. for the virtual table, for supported interfaces, etc., so there you should defintely read field by field. Blitting a class instance is not a good idea at all. – Rudy Velthuis Mar 04 '16 at 12:31
  • @DavidHeffernan: it is extremely hard to make a Delphi class instance binary compatible with anything else, while it is very easy for records. – Rudy Velthuis Mar 04 '16 at 12:32
  • @RudyVelthuis You are not following. Nobody is suggesting using a Delphi class. The Delphi code isn't changing. We have a binary data file that was created by blitting a Delphi record. Now, here we are talking about C# classes. They can be marshalled fine. They need a `StructLayout` attribute. The reason you might use classes is that they are reference types. I for one do this with my own C# interop library so I know it is sometimes a sound approach. – David Heffernan Mar 04 '16 at 12:33
  • OK, I was indeed not quite following. – Rudy Velthuis Mar 04 '16 at 12:34
  • @pfinferno I've added some code to show you how to blit the structure. If you changed `Testrec` to be a class then that code would still work unchanged. You obviously need to keep the `StructLayout` attribute so that the class can be formatted correctly. – David Heffernan Mar 04 '16 at 12:40
  • Thank you. Going to test it now. One last question, how do I set up the stream w/ the necessary filepath, filemode, etc.? I edited my post to reflect how I originally set-up the BinaryReader I was going to use to reflect the Delphi Tfilestream properties being used. – pfinferno Mar 04 '16 at 12:54
  • I don't really want to show you how to use C# file streams. That's outside the question. It's well documented. There are many good intro books on C#. Your supervisors should be giving you this basic training. – David Heffernan Mar 04 '16 at 13:01
  • I seem to have it working properly now! Thank you very much, I appreciate it. Going to look into blitting more, I have a feeling I'm going to be using it more in the future. – pfinferno Mar 04 '16 at 13:10