Как установить порядок следования байтов при преобразовании в шестнадцатеричные строки или из них

Чтобы преобразовать целое число в строку в шестнадцатеричном формате, я использую ToString("X4") следующим образом:

int target = 250;    
string hexString = target.ToString("X4");

Чтобы получить целочисленное значение из строки в шестнадцатеричном формате, я использую метод Parse:

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

Однако машина, с которой я обмениваюсь данными, помещает байты в обратном порядке.

Чтобы сохранить данные образца, если я хочу отправить значение 250, мне нужна строка «FA00» (а не 00FA, которая является шестнадцатеричной строкой). Аналогично, если я получаю «FA00», мне нужно преобразовать это в 250, а не 64000.

Как установить порядок байтов этих двух методов преобразования?


person Ralph Shillington    schedule 31.01.2011    source источник


Ответы (2)


Это не встроенная опция. Так что либо выполняйте строковую работу, чтобы поменять местами символы, либо какой-то битовый сдвиг, т.е.

int otherEndian = (value << 16) | (((uint)value) >> 16);
person Marc Gravell    schedule 31.01.2011
comment
Спасибо, что подтвердили то, что я подозревал. Мне нравится ваше предложение о смещении битов, однако я не уверен, что оно работает должным образом. например: значение переменной = 250; var otherEndian = (значение ‹‹ 16) | (((uint)значение) ›› 16); var test = (otherEndian ‹‹ 16) | (((uint)otherEndian) ›› 16); (тест==значение).Дамп(); приводит к ЛОЖЬ. разве это не должно быть ИСТИНА? - person Ralph Shillington; 31.01.2011
comment
@Ralph - вернемся к ПК через несколько часов; тогда проверю - person Marc Gravell; 31.01.2011
comment
У меня сработало нормально, хотя мне нужно было добавить явное приведение к int, чтобы заставить его скомпилироваться: int value = 250;int otherEndian = (value << 16) | ((int)(((uint)value) >> 16));value = otherEndian;otherEndian = (value << 16) | ((int)(((uint)value) >> 16)); Вторые две строки я тестировал, что повторный запуск тех же шагов даст мне 250. - person Brian; 31.01.2011
comment
@Brian re 16-битный (не 8) удаленный ответ, который должен был соответствовать указанным вводам/выводам. - person Marc Gravell; 31.01.2011
comment
Марк: кажется, вы с Брайаном выяснили, почему вы здесь сдвигаетесь на 16 бит вместо 8, но, читая ответы и комментарии, я до сих пор не могу понять, почему. Было бы полезно, если бы вы могли отредактировать свой ответ, чтобы уточнить, почему вы выбрали именно такой подход; сдвиг на 16 бит очень необычен для замены 16-битного значения и, похоже, даже не выполняет операцию, которую запрашивает исходный вопрос. Спасибо! - person Peter Duniho; 08.08.2016
comment
@PeterDuniho Я не могу представить себе, зачем я это сделал ... очень странно; ваш комментарий кажется очень актуальным - person Marc Gravell; 09.08.2016
comment
Ах хорошо. Что ж, если вы помните и обновляете пост, я не возражаю против комментария, предупреждающего меня, чтобы я мог узнать. Я понимаю, что из-за того, что вы написали пять лет назад, это воспоминание может никогда не прийти. Но мне все же любопытно. :) - person Peter Duniho; 09.08.2016

Ответ Марка, кажется, в силу того, что он был принят, касается исходной проблемы ОП. Однако из текста вопроса мне не совсем понятно, почему. Это все еще требует замены байтов, а не пар байтов, как это делает ответ Марка. Я не знаю ни одного достаточно распространенного сценария, в котором замена битов 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");

Обратите внимание, что вышеприведенное предполагает, что мы фактически имеем дело с 16-битными значениями, хранящимися в 32-битной переменной int. Он будет обрабатывать любой ввод в 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().

Эти методы работают только с тремя основными целочисленными типами: Int16, Int32 и Int64 (в C# short, int и long соответственно). Они также возвращают тот же тип, который был передан им, поэтому вы должны быть осторожны с расширением знака. Исходный пример в вопросе можно решить так:

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 бит. Это можно решить без маскирования, просто используя более подходящие типы данных для переменных (например, short, когда ожидается, что данные будут подписанными 16-битными значениями).

Наконец, я отмечу, что методы HostToNetworkOrder() и NetworkToHostOrder(), поскольку они только меняют местами байты, эквивалентны друг другу. Они оба меняют местами байты, когда архитектура машины имеет обратный порядок байтов . И действительно, реализация .NET просто для NetworkToHostOrder() для вызова HostToNetworkOrder(). Существует два метода, в основном для того, чтобы API .NET соответствовал исходному API сокетов BSD, который включал такие функции, как htons() и ntohs(), и этот API, в свою очередь, включал функции для обоих направлений преобразования, главным образом, чтобы в коде было ясно, получает ли кто-то данные из сети или отправка данных в сеть.


† И ничего не делать, когда архитектура машины имеет обратный порядок байтов, они бесполезны в качестве обобщенных функций перестановки байтов. Скорее ожидается, что сетевой протокол всегда будет с обратным порядком байтов, и эти функции используются для обеспечения того, чтобы байты данных менялись местами в соответствии с любой архитектурой машины.

person Peter Duniho    schedule 08.08.2016