4
\$\begingroup\$

I want to use structs as a container for data packets for asynchronous networking in C#. Found out that you can create a union style struct without the need to mark the struct itself as unsafe--instead marking the field as unsafe.

Example:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Size = 8, Pack = 4)]
struct DataPacketStruct {
    private unsafe fixed byte bytes[8];
    // Publicly accessible fields.
    public byte header;
    public int size;
    
    public void Serialize(ref byte[] buffer, int startIndex) {
        unsafe {
            for (int i = 0; i < 8; i++) buffer[startIndex + i] = bytes[i];
        }
    }

    public void Deserialize(byte[] buffer) {
        unsafe {
            for (int i = 0; i < 8; i++) bytes[i] = buffer[i];
        }
    }
    
    public int SizeOf() { unsafe { return sizeof(DataPacketStruct); } }
}

I know I need array out of bounds checking--but apart from that. Potential downsides or undefined behaviors? Or is this usage valid? Also are there any potential performance concerns or any alternatives with similar performance?

Also an issue I can see right off the bat is not having compile-time numeric constants like in C++. Unfortunately have to hard code in the field offsets and structure size because of this: Size = 8 and byte[8].

\$\endgroup\$
4
  • \$\begingroup\$ Afaik, .NET has enough powerful networking API that doesn't require any unsafe stuff. What's the purpose of the structure? Can you show and explain the usage example? Explicit structure can be needed for marshaling it to P/Invoke unmanaged function call e.g. send it as byte array. As option you may use BinaryWriter as managed alternative if you need just a byte array not P/Invoke. \$\endgroup\$
    – aepot
    Nov 28, 2020 at 22:38
  • 1
    \$\begingroup\$ Did you see this? Also consider Buffer.BlockCopy from System.Buffers as loop replacement. \$\endgroup\$
    – aepot
    Nov 28, 2020 at 22:44
  • \$\begingroup\$ @aepot it's a nice convenient way of creating packet data structures. You can write a byte stream to a struct and use the struct as a nice wrapper to represent your packet data. \$\endgroup\$
    – FatalSleep
    Nov 29, 2020 at 1:50
  • 1
    \$\begingroup\$ @aepot no, but thank you I'll be using it now. \$\endgroup\$
    – FatalSleep
    Nov 29, 2020 at 1:50

1 Answer 1

4
\$\begingroup\$

There's a safe way to do that using System.Runtime.InteropServices.Marshal (link).

[StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
struct DataPacketStruct
{
    public byte Header { get; set; }
    public int Size { get; set; }

    public void Serialize(byte[] buffer, int startIndex)
    {
        int size = SizeOf();
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(this, ptr, true);
        Marshal.Copy(ptr, buffer, startIndex, size);
        Marshal.FreeHGlobal(ptr);
    }

    public static DataPacketStruct Deserialize(byte[] buffer, int startIndex)
    {
        var result = new DataPacketStruct();
        int size = result.SizeOf();
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.Copy(buffer, startIndex, ptr, size);
        result = (DataPacketStruct)Marshal.PtrToStructure(ptr, typeof(DataPacketStruct));
        Marshal.FreeHGlobal(ptr);
        return result;
    }

    public int SizeOf() => Marshal.SizeOf(this);
}

Also I used public properties and followed the Naming Guidelines in the example.

static void Main(string[] args)
{
    var data = new DataPacketStruct() { Header = byte.MaxValue, Size = int.MaxValue };
    byte[] a = new byte[8];
    data.Serialize(a, 0);
    Console.WriteLine(string.Join(" ", a.Select(x => x.ToString("X2"))));
    var data2 = DataPacketStruct.Deserialize(a, 0);
    Console.WriteLine(JsonSerializer.Serialize(data2, new JsonSerializerOptions { WriteIndented = true }));
    Console.ReadKey();
}

Output:

FF 00 00 00 FF FF FF 7F
{
  "Header": 255,
  "Size": 2147483647
}
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Nice! I've seen this implementation using Marshal, problem is if you run a test on it, I bet it's not nearly as fast. \$\endgroup\$
    – FatalSleep
    Nov 29, 2020 at 9:24
  • 1
    \$\begingroup\$ @FatalSleep there's no performance concern in the question above. Anyway, you may test it as you have the more real environment. In production environment I prefer something like JSON and HTTPS to transfer the data. It's more safe in terms of security. Writing network input directly to memory can be done only if you 100% trust the source. For socket-like data exchange i use WebSockets. That allows me to make browser client and desktop or mobile app clients using the same server. \$\endgroup\$
    – aepot
    Nov 29, 2020 at 9:34
  • \$\begingroup\$ I have updated my question accordingly. I like the answer, but it doesn't really address the question, more so just says do it safe. Which, while I am using an unsafe methodology here, I did it in a specific manner to avoid having to mark the struct unsafe. Which normally one wouldn't do, but from a safety aspect not much is happening here IMO that is entirely problematic. \$\endgroup\$
    – FatalSleep
    Nov 29, 2020 at 9:51
  • 1
    \$\begingroup\$ @FatalSleep Thank you for the feedback. I'm not sure that your way guarantee GC will not move the fields across the memory. You're making unsafe access to managed fields with no fixed directive which tells GC not to move that fields. That's why i can't confirm the correctness of your solution. By the first sight it looks suspicious. Maybe I'm wrong. \$\endgroup\$
    – aepot
    Nov 29, 2020 at 10:10
  • \$\begingroup\$ could you put that in your answer? \$\endgroup\$
    – FatalSleep
    Nov 29, 2020 at 22:24

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.