【问题标题】:C# Object type conversion to byte[], send over TCP and converting back to doubleC# 对象类型转换为 byte[],通过 TCP 发送并转换回 double
【发布时间】:2016-10-12 08:21:09
【问题描述】:

我对 C# 编程相当陌生,通常处理 PLC 编程和 HMI/SCADA 系统开发。最后一个只涉及一点点 C/VBS 脚本。

最近,在我的一个项目中,我将最初在 LabVIEW 中开发的 SCADA 系统升级/迁移到 Siemens WinCC 应用程序。 在 LabVIEW 项目中,有一部分负责通过 TCP 套接字将标签值(与标签名称捆绑在一起并转换为字符串的双精度值)发送到另一台运行 C# 应用程序(TCP 客户端)的 PC,该 PC 正在接收字节数组并将其转换为双倍值。

LabVIEW TCP Server Example

以及在另一台PC上运行的TCP Client的部分代码。

TcpClient                   _client;
NetworkStream               _stream = null;
byte[]                      _data = new byte[8192];

{...}
try
{
    _client     = new TcpClient(properties.IpAddress, properties.Port);
}
{...}
    try
    {
        _stream     = _client.GetStream();
{...}
        int NumBytesInRecvBuffer = _stream.Read(_data, 0, 8192);

{...}
            if(_data[3] == paramCount)
            {
                for(int i = 0; i < paramCount; i++)
                {
                    double Value = MakeDouble(_data, 4+(i*8));
                    fmacsParams[i].NumericalValue = Value;
                }
            }
    }

TCP 客户端中的MakeDouble() 函数。

private unsafe double MakeDouble(byte[] data, int StartIndex)
{
    double result;
    byte* b = (byte*)&result;
    b[0] = data[StartIndex + 7];
    b[1] = data[StartIndex + 6];
    b[2] = data[StartIndex + 5];
    b[3] = data[StartIndex + 4];
    b[4] = data[StartIndex + 3];
    b[5] = data[StartIndex + 2];
    b[6] = data[StartIndex + 1];
    b[7] = data[StartIndex + 0];
    return(result);         
}

我设法用 C# 开发了一个简单的应用程序,它是一个 TCP 侦听器,并从 WinCC 运行时读取标签值作为对象

 object ft107Flow = oType.InvokeMember("GetValue", System.Reflection.BindingFlags.InvokeMethod, null, wincc, new object[] { "FT107_Flow" });

然后将其转换为双精度和字节数组。最后,数组中的所有字节都用函数 SwapBytes() 交换并放入数据数组,以便它可以作为字节数组通过 TCP 发送。

{...}
const int paramCount = 30;
double[] WinCC_Tags = new double[paramCount];

WinCC_Tags[0] = Convert.ToDouble(ft107Flow);
{...}
byte[] data = new byte[4 + paramCount * 8];
Array.Clear(data, 0, data.Length);

byte[] UnswappedByte;
byte[] SwappedByte;

data[3] = paramCount;

for (int i = 0; i < paramCount; i++)
{
     int j;

     UnswappedByte = BitConverter.GetBytes(WinCC_Tags[i]);
     SwappedByte = SwapBytes(UnswappedByte, 0);

     j = (4 + i * 8);

     data[j] = SwappedByte[0];
     data[j + 1] = SwappedByte[1];
     data[j + 2] = SwappedByte[2];
     data[j + 3] = SwappedByte[3];
     data[j + 4] = SwappedByte[4];
     data[j + 5] = SwappedByte[5];
     data[j + 6] = SwappedByte[6];
     data[j + 7] = SwappedByte[7];
}

我目前面临的问题是客户端收到的这些值不一样。由于这种双重甚至三重转换(对象->双,双->字节数组;字节数组->双),似乎精度丢失了。

例如,在 WinCC 运行系统中,变量值读取为 15.3。当它转换为双精度时,它等于15.300000190734863。然后data[12..19] = {0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00}。

客户端收到的数据如下:

_data[12..19] = {0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00}

Value = 15.001172065734863

客户端接收到的数据(字节数组)看似相同,但转换后的值与WinCC Runtime中的值不相等。

能否请您确认是否有一种方法可以修改 TCP 侦听器端,以便 TCP 客户端使用 MakeDouble() 函数在转换后接收正确的数据,而无需接触 TCP 客户端代码(不允许修改它)。

【问题讨论】:

  • 简单:byte[] data = new byte[] { 0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00 };双数 = BitConverter.ToDouble(data.Reverse().ToArray(), 0);我得到 15.300。
  • 他的 make MakeDouble() 功能运行良好

标签: c# sockets tcp labview


【解决方案1】:

我建议您不要自己交换字节 this 包。这是一个 BigEdian BinaryReader/Writer

你可以这样使用它

using (MemoryStream stream = new MemoryStream())
using (BeBinaryReader BeReader = new BeBinaryReader(stream))
{
    stream.Position = 3;
    if(BeReader.ReadByte() == paramCount)
    for (int i = 0; i < paramCount; i++)
    {
       double Value = BeReader.ReadDouble();
       fmacsParams[i].NumericalValue = Value;
    }
 }

【讨论】:

  • 问题是我不允许修改 TCP 客户端代码。
  • 但是为了读取和写入您的数据服务器端,您可以使用 BigEdian BinaryReader/Writer
  • 有什么方法可以证明函数 MakeDouble() 中的内容将字节数组转换为双精度,从而失去精度并因此返回略有不同的值?
  • byte[] value = BitConverter.GetBytes((double)15.300000190734863); Array.Reverse(value); Console.WriteLine(MakeDouble(value,0)); 给我 15.300000190734863 所以它似乎工作
  • 这很有趣。同一段代码给了我 15.0011720657349
【解决方案2】:

我不太熟悉 C# 如何处理不安全代码和指针,但如果我从 C 的角度回答这个问题,看起来第 6 个字节被某些东西覆盖了。

15.300000190734863 和 15.0011720657349 之间的区别是 {0x00, 0x00, 0x00, 0xa0, 0x99, 0x99, 0x2e, 0x40} 和 {0x00, 0x00, 0x00, 0xa0, 0x99, 0x00, 0x2e, 0x40}。

试试下面的代码

    private double MakeDouble(byte[] data, int StartIndex)
    {
        double result;
        byte[] b = new byte[8];
        b[0] = data[StartIndex + 7];
        b[1] = data[StartIndex + 6];
        b[2] = data[StartIndex + 5];
        b[3] = data[StartIndex + 4];
        b[4] = data[StartIndex + 3];
        b[5] = data[StartIndex + 2];
        b[6] = data[StartIndex + 1];
        b[7] = data[StartIndex + 0];
        result = BitConverter.ToDouble(b, 0);
        return (result);
    }

【讨论】:

  • 但是,如果在两台不同的 PC 上使用相同的软件编译相同的代码,实际上会导致不同的结果?
  • 一般来说(不是专门针对 C#),具有不同的编译器或 CLR 版本和/或设置可能会导致不同的输出,因为系统可以尝试以不同的方式对其进行优化。在 C 代码中,如果代码不符合正确的编码标准,优化器可能会导致您的代码执行不可预测,即使以未经优化的方式编译时它会按预期执行;我只是举个例子。 2个问题:a)您是否尝试过运行上述代码? b) 您是否尝试过在一台机器上构建并在另一台机器上运行构建?
猜你喜欢
  • 2013-01-06
  • 2018-01-02
  • 1970-01-01
  • 1970-01-01
  • 2021-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多