通过TCP / ip向C#读取/写入C结构 [英] Read/write C structures to/from C# over TCP/ip

查看:59
本文介绍了通过TCP / ip向C#读取/写入C结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述





可能要问太多了,但我会试一试。我已经在C工作了将近30年b / B $ B $ C ++,差不多15年,但我是C#的新手。



我们有一个大的它在C中的应用程序。它建立了一个用于

管理的tcp / ip服务器。目前,有一个x-windows GUI客户端(在C中)

连接到此服务器以配置和监视应用程序。我们

想要用PC上运行的东西替换旧的x-windows客户端。

我们被告知C#非常合适。 />


服务器应用程序和客户端GUI之间的所有通信都是通过

消息交换,高级消息格式为:



< length> <报头GT; [可选< data>]

2字节固定长度0或更多固定长度,或字符串



- <长度>是一个16位无符号整数,总消息大小(不是

,包括< length>本身)。



- <报头GT;是一个C风格的结构,混合了各种

大小(8,16,32和64位)的签名/无符号整数,一些C风格的固定长度,以null结尾

8位字符的字符串,以及一些位字段(unsigned int,其中每个

位用于表示单个标志)



- < data>字段(如果存在)将是一个或多个C样式结构或

a字符串,所有字符都基于< header>中的消息类型。

< data>中使用了100多种不同的结构。字段(各种

配置项,统计元素,......)。



我在模仿C-时遇到一些困难C#中的struct / C-strings。我在网上看到了很多关于编组,不安全的代码,非托管代码,

LPStr,UTF8,......的内容,但还没看到一个简洁的例子of:


a)在C-struct(带有C字符串)中读取tcp / ip并使其可用于C#ec



b)做反向(重建C-struct发送)



这是一个虚构的,40个八位字节的C结构用作示例中的标题(u16

是无符号的16位int,s16是带符号的16位int,...):



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;





我尝试过:



编组,不安全代码,非托管代码,

LPStr,UTF8,...,



What I have tried:

marshalling, unsafe code, unmanaged code,
LPStr, UTF8, ...,

推荐答案

我同意Dave K,你可以使用c#二进制序列化,你也可以使用Google protobuf,JSON或者二进制JSON作为'编码' - 对我来说,这很容易...



'硬件'是,你的一面有'Spec',定义了100种不同的结构 - 什么时候需要改变?你有2个地方需要改变和测试,c ++方面和c#方。



我建议,根据经验,你



a)找到你感觉舒服的任何序列化(作为一般技术)方法 - 花时间去做正确的事情,考虑未来的增长等



b)'编码'你的规范,这样你就可以生成你的c ++结构,当然还有c#decode例程,并且希望你的测试工具所需的任何东西都可以自动作为构建过程的一部分 - 在某种程度上,protobuf可能会这里你的生活更轻松,但有很多工具/方法



顺便说一句,如果你曾经处理过ISO 8583,你会知道为什么我建议这个方法
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


如何从TCP / IP读取取决于您自己。听起来你已经有了一个有效的客户端/服务器。



选项#1:

将核心TCP / IP客户端代码转换为DLL并使用C#进行PInvoke:

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#:
[DllImport("tcpipclient.dll", CallingConvention.Cdecl)]
public static extern TestHeader GetHeader();



为此,请使用 StructLayoutAttribute MarshalAsAttribute 。这样你就可以手动对齐C#中的结构并控制托管的< - >非托管编组。举个例子,C#版本可以是:


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:

[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;
}



我使用 byte 代替 char 是因为char通常是C中的1个字节,C#中是2个字节。这只是将其加载到 byte 然后稍后进行转换的简单方法。处理 flag1 可以通过使用枚举完成,如下所示:


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:

[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



测试您想要的个别标志。我更喜欢使用 testFlag.HasFlag(TestHeaderFlags.Flag2)



关于这种方法的说明:

我还没有设置测试这个,所以如果这不起作用你可能想看看 LayoutKind.Explicit [ ^ ]。要检查的另一件事可能是在 unsafe struct 中使用 fixed char text [4]



选项#2:

使用您想要的任何方法从TCP读取字节流/ IP然后使用 BitConverter 来分离流并制作您的对象。示例:


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:

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 :)


这篇关于通过TCP / ip向C#读取/写入C结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆