Click here to Skip to main content
15,891,796 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi,

May be too much to ask, but I'll give it a go. I've worked in C for almost 30
years, C++ for almost 15, but am new to C#.

We have a large application in C. It stands up a tcp/ip server for
administration. Currently, there is an x-windows GUI client (in C) that
connects into this server for configuring and monitoring the application. We
want to replace the old x-windows client with something that'll run on a PC.
We've been told that C# is a good fit.

All communication between the server application and the client GUI is via
message exchange, with the high-level message format being:

<length> <header> [optional <data>]
2-byte fixed-length 0-or-more fixed-length, or string

- The <length> is a 16-bit unsigned integer with total message size (not
including the <length> itself).

- The <header> is a C-style struct with a mix of signed/unsigned ints of various
sizes (8, 16, 32, and 64-bit), some C-style fixed-length, null-terminated
strings of 8-bit characters, and some bit-fields (unsigned int where each
bit is used to denote an individual flag)

- The <data> field (if it exists) will be either one or more C-style struct's or
a string of characters, all based on the message-type within the <header>.
There are over 100 different structs used in the <data> field (various
configuration items, statistic elements, ...).

I'm having a bit of difficulty mimicking the C-struct/C-strings in C#. I've
seen online a bunch of stuff on marshalling, unsafe code, unmanaged code,
LPStr, UTF8, ..., but haven't seen a concise example of:

a) tcp/ip reading in a C-struct (with C-strings) and making it useable in C#ec

b) doing the reverse (re-building the C-struct to send)

Here is a fictional, 40-octet C struct to use as the header in examples (u16
is an unsigned 16-bit int, s16 is a signed 16-bit int, ...):

typedef struct TestHeader
{
  u16  messageType;
  s16  num1;
  char text1[4];
  char text2[8];
  u64  num2;
  s32  num3;
  u8   num4;
  u8   num5;
  u16  flag1;   // each bit used as a flag
  char text3[4];
  char text4[4];
} TestHeader;


What I have tried:

marshalling, unsafe code, unmanaged code,
LPStr, UTF8, ...,
Posted
Updated 21-Feb-17 16:59pm
v2
Comments
Patrice T 21-Feb-17 18:19pm    
And what is the question ?
Dave Kreskowiak 21-Feb-17 18:58pm    
I'm not entirely sure what you're having a problem with, but as you're coming from a C/C++ background, you may be overthinking this a bit.

Google for "C# binary serialization". See what that does for you.

While I agree with Dave K, you could use c# binary serialisation, you could also use Google protobuf, JSON or perhaps Binary JSON as the 'encoding' - to me, that's the easy part ...

The 'Hard Part' is, you have a 'Spec' on one side, defining some 100 different structs - what happens when anything needs changing ? you have 2 places that you'll need to change and test, the c++ side and the c# side.

I suggest, from experience, you

a) find whatever serialisation (as a general technique) method you feel comfortable with - take time to get it right, account for future growth etc

b) 'codify' your specs, so that you can generate your c++ structs and certainly your c# decode routines, and hopefully anything needed for your test harness automatically as part of your build process - to some extent, protobuf may make your life 'easier' here, but there are many tools/approaches

btw, if you've ever dealt with ISO 8583, you'll know why I suggest this approach
 
Share this answer
 
v3
How you read from TCP/IP is up to you. It sounds like you already had a client/server that worked.

Option #1:
Turn the core TCP/IP client code into a DLL and PInvoke it in C#:
C#
[DllImport("tcpipclient.dll", CallingConvention.Cdecl)]
public static extern TestHeader GetHeader();

To do this use the StructLayoutAttribute and MarshalAsAttribute. This let's you align a structure in C# manually and control managed<->unmanaged marshalling. Given your example, the C# version could be:
C#
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestHeader
{
    [MarshalAs(UnmanagedType.U2)]
    public ushort messageType;
    [MarshalAs(UnmanagedType.I2)]
    public short num1;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public byte[] text1;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] text2;
    [MarshalAs(UnmanagedType.U8)]
    public ulong num2;
    [MarshalAs(UnmanagedType.I4)]
    public int num3;
    [MarshalAs(UnmanagedType.U1)]
    public byte num4;
    [MarshalAs(UnmanagedType.U1)]
    public byte num5;
    [MarshalAs(UnmanagedType.U2)]
    public ushort flag1;   // each bit used as a flag
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public byte[] text3;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public byte[] text4;
}

The reason I use byte instead of char is because char is generally 1 byte in C and 2 bytes in C#. This is just an easy way of loading it into byte and then converting later. Handling flag1 could be accomplished by using an enum like this:
C#
[Flags]
public enum TestHeaderFlags : ushort
{
    None = 0x00,
    Flag1 = 0x01,
    Flag2 = 0x02,
    Flag3 = 0x04,
    Flag4 = 0x08,
    Flag5 = 0x10,
    Flag6 = 0x20,
    Flag7 = 0x40,
    Flag8 = 0x80
}

static void Main(string[] args)
{
    ushort test = 0x83;
    TestHeaderFlags testFlag = (TestHeaderFlags) Enum.ToObject(
        typeof(TestHeaderFlags), test);
    Console.WriteLine(testFlag);
    Console.ReadKey();
}   

//Output
Flag1, Flag2, Flag8

Test for individual flags however you'd like. I prefer using testFlag.HasFlag(TestHeaderFlags.Flag2).

Notes on this approach:
I haven't made a setup to test this so if this doesn't quite work you might want to look at LayoutKind.Explicit[^]. Another thing to check out might be using fixed char text[4] in an unsafe struct.

Option #2:
Use whatever method you'd like to read the byte stream from TCP/IP then use BitConverter to separate out the stream and make your object. Example:
C#
public TestHeader DeserializeHeader(byte[] stream)
{
    TestHeader header;
    header.messageType = BitConverter.ToUInt16(stream, 0);
    header.num1 = BitConverter.ToInt16(stream, 2);
    header.num2 = BitConverter.ToUInt64(stream, 16);
    header.num3 = BitConverter.ToInt32(stream, 24);
    header.num4 = stream[28];
    header.num5 = stream[29];
    header.flag1 = BitConverter.ToUInt16(stream, 30);

    header.text1 = new byte[4];
    header.text3 = new byte[4];
    header.text4 = new byte[4];
    for (int i = 0; i < 4; i++)
    {
        header.text1[i] = stream[4 + i];
        header.text3[i] = stream[32 + i];
        header.text4[i] = stream[36 + i];
    }
    header.text2 = new byte[8];
    for (int i = 0; i < 8; i++)
        header.text2[i] = stream[8 + i];
    return header;
}

Turn the process around to serialize. I'm sure there are more ways to go about it but these are what came to mind :)
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900