Репликация заполнения структуры C в Java - PullRequest
4 голосов
/ 08 мая 2009

Согласно здесь , компилятор C будет выгружать значения при записи структуры в двоичный файл. Как говорит пример в ссылке, при написании структуры, подобной этой:

struct {
 char c;
 int i;
} a;

в двоичном файле, компилятор обычно оставляет безымянное, неиспользуемое отверстие между полями char и int, чтобы гарантировать правильное выравнивание поля int

Как я могу создать точную копию двоичного выходного файла (сгенерированного в C), используя другой язык (в моем случае, Java)?

Существует ли автоматический способ применения заполнения Си в выходных данных Java? Или мне нужно просмотреть документацию компилятора, чтобы увидеть, как он работает (кстати, компилятор g ++).

Ответы [ 11 ]

14 голосов
/ 08 мая 2009

Не делайте этого, это хрупко и приведет к ошибкам выравнивания и порядка байтов.

Для внешних данных гораздо лучше явно определить формат в виде байтов и написать явные функции для преобразования между внутренним и внешним форматом, используя shift и маски (не объединение!).

8 голосов
/ 08 мая 2009

Это верно не только при записи в файлы, но и в память. Тот факт, что структура дополняется в памяти, приводит к тому, что заполнение отображается в файле, если структура записывается побайтово.

В общем, очень трудно с уверенностью воспроизвести точную схему заполнения, хотя я полагаю, что некоторые эвристики могли бы вас продвинуть довольно далеко. Помогает, если у вас есть объявление структуры, для анализа.

Как правило, поля размером более одного символа будут выровнены, так что их начальное смещение внутри структуры будет кратно их размеру. Это означает, что short s обычно будет на четных смещениях (делится на 2, при условии sizeof (short) == 2), в то время как double s будет на смещениях, кратных 8 и т. Д.

ОБНОВЛЕНИЕ : Именно по таким причинам (а также по причинам, связанным с порядком байтов), обычно плохая идея выгружать целые структуры в файлы. Лучше делать это по полю, например:

put_char(out, a.c);
put_int(out, a.i);

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

5 голосов
/ 08 мая 2009

Есть ли автоматический способ применения C заполнение в выводе Java? Или у меня есть просмотреть документацию компилятора чтобы увидеть, как это работает (компилятор g ++ кстати).

Ни. Вместо этого вы явно указываете формат данных / связи и реализуете эту спецификацию, а не полагаетесь на детали реализации компилятора C. Вы даже не получите один и тот же вывод от разных компиляторов C.

4 голосов
/ 08 мая 2009

Для совместимости посмотрите на класс ByteBuffer.

По сути, вы создаете буфер определенного размера, помещаете переменные () разных типов в разные позиции, а затем вызываете array () в конце, чтобы получить «необработанное» представление данных:

ByteBuffer bb = ByteBuffer.allocate(8);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.put(0, someChar);
bb.put(4, someInteger);
byte[] rawBytes = bb.array();

Но вам решать, где разместить отступы, т. Е. Сколько байтов пропустить между позициями.

Для чтения данных, записанных из C, обычно wrap () создает ByteBuffer вокруг некоторого байтового массива, который вы прочитали из файла.

Если это полезно, я написал больше о ByteBuffer .

2 голосов
/ 08 мая 2009

Удобный способ чтения / записи структур C в Java - это использование класса javolution Struct (см. http://www.javolution.org).). Это не поможет вам автоматически дополнять / выравнивать данные, но делает работу с raw данные, хранящиеся в ByteBuffer, гораздо удобнее. Если вы не знакомы с javolution, стоит посмотреть, так как там много других интересных вещей.

1 голос
/ 08 мая 2009

Я настоятельно рекомендую буферы протокола именно для этой проблемы.

1 голос
/ 08 мая 2009

вы можете попробовать preon :

Preon - это библиотека Java для создания кодеков для сжатых битовых данных в декларативный (на основе аннотаций) способ. Думайте JAXB или Hibernate, но тогда для двоичного закодированные данные.

может обрабатывать двоичные данные Big / Little endian, выравнивание (заполнение) и различные числовые типы наряду с другими функциями Это очень хорошая библиотека, мне она очень нравится

мои 0,02 $

1 голос
/ 08 мая 2009

В Java размер типов данных определяется спецификацией языка. Например, тип byte равен 1 байту, short равен 2 байта и т. Д. Это не похоже на C, где размер каждого типа зависит от архитектуры.

Следовательно, было бы важно знать, как отформатирован двоичный файл, чтобы можно было прочитать файл в Java.

Может потребоваться предпринять шаги, чтобы убедиться, что поля имеют определенный размер, чтобы учесть различия в компиляторе или архитектуре. Упоминание о выравнивании предполагает, что выходной файл будет зависеть от архитектуры.

1 голос
/ 08 мая 2009

Это отверстие настраивается, компилятор имеет переключатели для выравнивания структур на 1/2/4/8 байтов.

Итак, первый вопрос: какое именно выравнивание вы хотите смоделировать?

0 голосов
/ 08 мая 2009

Насколько я понимаю, вы говорите, что не управляете выводом C-программы. Вы должны принять это как дано.

Так вам нужно прочитать этот файл для какого-то определенного набора структур, или вам нужно решить это в общем случае? Я имею в виду, проблема в том, что кто-то сказал: «Вот файл, созданный программой X, вы должны прочитать его на Java»? Или они ожидают, что ваша Java-программа прочитает исходный код C, найдет определение структуры и затем прочитает его на Java?

Если у вас есть определенный файл для чтения, проблема на самом деле не очень сложная. Либо, просмотрев спецификации компилятора C, либо изучив файлы примеров, выясните, где находится отступ. Затем, на стороне Java, прочитайте файл как поток байтов и постройте ожидаемые значения. По сути, я бы написал набор функций для чтения необходимого количества байтов из InputStream и превращения их в соответствующий тип данных. Как:

int readInt(InputStream is,int len)
  throws PrematureEndOfDataException
{
  int n=0;
  while (len-->0)
  {
    int i=is.read();
    if (i==-1)
      throw new PrematureEndOfDataException();
    byte b=(byte) i;
    n=(n<<8)+b;
  }
  return n;
}
...