如何在转换为或从十六进制字符串时设置字节序

4
为了将整数转换为十六进制格式的字符串,我使用ToString("X4"),如下所示:
int target = 250;    
string hexString = target.ToString("X4");

要从十六进制格式的字符串中获取整数值,我使用Parse方法:

int answer = int.Parse(data, System.Globalization.NumberStyles.HexNumber);

然而,我要交换数据的机器会以相反的顺序放置字节。

为了保持示例数据一致,如果我想发送值250,我需要一个字符串“FA00”(不是hexString的“00FA”),同样地,如果我得到“FA00”,我需要将其转换为250而不是64000。

我如何设置这两种转换方法的字节顺序?

2个回答

5

马克的答案似乎已经解决了OP最初的问题,因为它已被接受。但是,从问题文本中并不清楚为什么会这样。这仍然需要交换字节,而不是像马克的答案一样交换字节对。我不知道有任何合理常见的情况下按16位一次地交换比特是有意义或有用的。

根据所述要求,我认为写成以下形式更有意义:

int target = 250; // 0x00FA

// swap the bytes of target
target = ((target << 8) | (target >> 8)) & 0xFFFF;

// target now is 0xFA00
string hexString = target.ToString("X4");

请注意,上述假设我们实际处理的是存储在32位int变量中的16位值。它将处理16位范围内的任何输入(请注意需要屏蔽掉上16位,因为它们会被<<运算符设置为非零值)。
如果要交换32位值,则需要使用以下代码:
int target = 250; // 0x00FA

// swap the bytes of target
target = (int)((int)((target << 24) & 0xff000000) |
    ((target << 8) & 0xff0000) |
    ((target >> 8) & 0xff00) |
    ((target >> 24) & 0xff));

// target now is 0xFA000000
string hexString = target.ToString("X8");

需要掩码才能隔离我们要移动到特定位置的位。在与其他三个字节或之前,将<< 24的结果转换回int是必需的,因为0xff000000是一个uint (UInt32) 字面量,并导致 &表达式扩展为long (Int64)。否则,每个|运算符都会产生编译器警告。


无论如何,由于这在大多数网络场景中经常出现,值得注意的是,.NET提供了可以帮助完成此操作的辅助方法:HostToNetworkOrder()NetworkToHostOrder()。在这个上下文中,“网络顺序”始终是大端字节序,“主机顺序”是用于托管当前进程的计算机上使用的任何字节顺序。

如果您知道您正在接收的数据是大端字节序,并且想要能够在您的进程中正确解释这些数据,那么您可以调用NetworkToHostOrder()。同样地,如果您需要在期望大端字节序的上下文中发送数据,则可以调用HostToNetworkOrder()

这些方法仅适用于三种基本整数类型:Int16Int32Int64 (在C# 中分别为shortintlong)。它们还返回传递给它们的相同类型,因此必须小心符号扩展。原始问题中的示例可以这样解决:

int target = 250; // 0x00FA

// swap the bytes of target
target = IPAddress.HostToNetworkOrder((short)target) & 0xFFFF;

// target now is 0xFA00
string hexString = target.ToString("X4");

再次强调,必须进行掩码处理,否则该方法返回的short值将被符号扩展为32位。如果结果中设置了第15位(即0x8000),那么最终的int值也将有其最高的16位设置。可以通过使用更合适的数据类型(例如当数据预期为带符号16位值时使用short)来解决此问题,而无需进行掩码处理。
最后,我要指出的是,由于HostToNetworkOrder()NetworkToHostOrder()方法只交换字节,因此它们彼此等效。当机器架构为小端时,它们都交换字节。实际上,.NET实现仅仅是让NetworkToHostOrder()调用HostToNetworkOrder()。存在这两个方法主要是为了使.NET API与原始的BSD sockets API相匹配,该API包括诸如htons()ntohs()之类的函数,并且该API又包括了用于转换方向的两个函数,主要是为了在代码中清楚地表明是否从网络接收数据或向网络发送数据。
† 当机器架构为大端时,它们不起作用...它们不是通用的字节交换函数。相反,期望网络协议始终为大端,并且使用这些函数来确保数据字节与机器架构匹配。

4

这不是内置选项。所以要么进行字符串操作来交换字符位置,要么进行位移操作,即:

int otherEndian = (value << 16) | (((uint)value) >> 16);

感谢确认我的猜测。我喜欢您位移位的建议,但我不确定它是否按预期工作。例如: var value = 250; var otherEndian = (value << 16) | (((uint)value) >> 16); var test = (otherEndian << 16) | (((uint)otherEndian) >> 16); (test==value).Dump(); 结果是FALSE。难道不应该是TRUE吗? - Ralph Shillington
对我来说运行得很好,尽管我需要添加一个显式的int类型转换才能使其编译通过:int value = 250;int otherEndian = (value << 16) | ((int)(((uint)value) >> 16));value = otherEndian;otherEndian = (value << 16) | ((int)(((uint)value) >> 16)); 第二和第三行是我测试重复运行相同步骤是否会给我返回250。 - Brian
Marc:你和Brian似乎已经弄清楚了为什么在这里要移动16位而不是8位,但是阅读答案和评论后,我仍然无法理解原因。如果您能编辑您的答案并详细说明为什么选择这种方法将会很有帮助;对于交换16位值来说,移动16位是非常不寻常的,甚至似乎并没有实际执行原始问题所要求的操作。谢谢! - Peter Duniho
@PeterDuniho 我真的想不出我为什么会那样做...非常奇怪;你的评论似乎非常有道理。 - Marc Gravell
啊,好的。如果你记得并更新了帖子,我不介意收到一条评论提醒,这样我就可以知道了。我意识到对于你五年前写的东西,那种回忆可能永远不会出现。但是我还是很好奇的。 :) - Peter Duniho
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接