0

I am working on a software that will exchange data with a Siemens PLC (industrial controller). In order for it to work, I need to be able to serialize and deserialize a byte array containing only the current values of the variables. The problem I am facing is that Serialize/Deserialize methods add a lot of information beyond the current value of a variable. The instance of the following class:

    [Serializable]
    public class VarMap
    {
            public byte var1;
            public int64 var2;
            public int32 var3;
    }

After serialized, needed to be a byte array containing one value after the other, each ocuppying their size in bytes: [var1 byte 1][var2 byte 1][var2 byte 2][var2 byte 3][var2 byte 4][var3 byte 1][var3 byte 2].

Any ideas how to make this happen dinamically according to the declaration of the class?

dbc
  • 104,963
  • 20
  • 228
  • 340
Felipe
  • 29
  • 3
  • This looks similar to [Serializing / Marshalling simple objects in C# to send over network, for an unmanaged C++ application to read](https://stackoverflow.com/q/28761167/3744182) and maybe [How to convert a structure to a byte array in C#?](https://stackoverflow.com/q/3278827/3744182), however you have specific requirements for packing, namely to not include alignment padding, and I'm not sure marshalling will handle those. – dbc Aug 25 '20 at 21:44
  • Maybe the solution from [the answer](https://stackoverflow.com/a/36484130/3744182) to [Struct alignment suggestions?](https://stackoverflow.com/q/36483893/3744182) is the better approach for you. – dbc Aug 25 '20 at 21:45

1 Answers1

0

For complex reference types that refer to other reference types, there is not a simple way to flatten those out into a pure sequence of bytes and there is a reason serializers have to use more complex formats to handle such cases.

That said, the type you show is what is called a blittable type.

Blittable types only contain fields of other primitive types whose bit pattern can be directly copied.

By changing your class to a struct, you could generically persist such types to disk directly. This does require the use of an unsafe block, but the code is relatively simple.

There are ways to do the same thing without using unsafe code, but using unsafe is the most straight-forward since you can just take a pointer and use that to write directly to disk, the network, etc.

First, change to a struct:

[StructLayout(LayoutKind.Sequential)]
public struct VarMap
{
    public byte var1;
    public long var2;
    public int var3;
}

Then, create one and write it directly to disk:

VarMap vm = new VarMap
{
    var1 = 254,
    var2 = 1234,
    var3 = 5678
};

unsafe
{
    VarMap* p = &vm;
    int sz = sizeof(VarMap);
    using FileStream fs = new FileStream(@"out.bin", FileMode.Create);
    ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(p, sz);
    fs.Write(span);
}

However, if you look at the result in a hex viewer you will notice something:

Viewing the written bytes

24 bytes were written to disk, not the 13 that we might assume based on the types of the fields in your data structure.

This is because structures are padded in memory for alignment purposes.

Depending on what the device you are sending these bytes to actually expects (and endian-ness might also be an issue), this kind of technique may or may not work.

If you need absolute control, you probably want to hand-write the serialization code yourself rather than trying to find some generic mechanism to do this. It might be possible using reflection, but probably simpler to just convert your fields to the raw byte representation directly.

mtreit
  • 789
  • 4
  • 6
  • This won't work because of the padding. As you suggested, looks like I will have to write my own solution. Using a foreach to go thru all the declared variables in the struct, how could I turn each one in its respective array of bytes? Is there a method that does that? – Felipe Aug 26 '20 at 11:23
  • Look at the BitConverter and BinaryPrimitives types. The former is slightly simpler to use, at the cost of always needing to allocate memory. If you aren't trying to hyper-optimize your memory allocations, just use BitConverter.GetBytes() on your various primitive type fields. – mtreit Aug 26 '20 at 18:59