Связывание программы на Фортране с произвольным бинарным файлом

Я хотел бы связать программу Fortran с произвольным двоичным файлом. Я использую gfortran и нашел здесь ту же задачу легко выполнить с помощью gcc, используя objcopy от binutils .

Однако я не могу заставить его работать с gfortran.

Вот рабочий тривиальный пример с gcc

Во-первых, build.c для создания файла данных, содержащего только двоичное представление числа pi=3,14...

#include <math.h>
#include <stdio.h>

int main() {
    FILE *f;

    double x = M_PI;
    f = fopen("data.bin", "wb");
    fwrite(&x, sizeof x, 1, f);
    fclose(f);
    return 0;
}

Затем cbin.c, чтобы напечатать число.

# include <stdio.h>

extern double val;

int main() {
    printf("%lf\n", val);
    return 0;
}

Затем, чтобы создать исполняемый файл, я делаю это

objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_data_bin_start=_val data.bin data.o
objdump -t data.o
gcc cbin.c data.o

Обратите внимание, что начало данных переименовано в _val.

Я пробовал следующее с gfortran

program forbin
    implicit none
    double precision :: val
    common val
    print *, val
end program

затем

objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_data_bin_start=_val_ data.bin data.o
objdump -t data.o
gfortran forbin.f95 data.o

Теперь начало данных переименовано в _val_, чтобы следовать соглашению об именах gfortran. Шаг компиляции работает, но при выполнении печатается число 0 вместо пи, значит что-то пошло не так. Я даже не уверен, что common - это правильно, и мне интересно, может ли быть какая-то путаница между адресом и значением. Кроме того, меня бы интересовали данные массива, а не один скаляр (я могу сделать это и в C, используя указатель на val).

Любая идея о том, что я должен сделать, чтобы сделать эту работу?

Если это важно, я работаю с gcc 4.9.1 из здесь, в Windows 7, 32-разрядная версия. Изначально я делаю это для создания DLL для использования в Excel. Я мог бы сделать это на C, но я бы предпочел, если возможно, подход на чистом Fortran.


изменить

Следуя предложению High Performance Mark и этой страницы из Stack Overflow, вот решение для скалярных данных.

module binmod
    use iso_c_binding, only: c_double
    real(c_double), bind(c) :: val
end module

program forbin
    use binmod
    implicit none
    print *, val
end program

И, конечно же, я использую имя C _val вместо _val_.

Однако, делая это с массивом, я все еще не понимаю, как использовать символы из data.o для объявления массива в Фортране. Я мог бы написать размеры вручную, но это не кажется очень надежным (было бы легко забыть обновить код Fortran, если бы я обновил данные).

Вот вывод из objdump -t data.o

00000000 l    d  .data  00000000 .data
00000000 g       .data  00000000 _val
00000008 g       .data  00000000 _binary_data_bin_end
00000008 g       *ABS*  00000000 _binary_data_bin_size

Глобальный _binary_data_bin_end — это маркер конца данных, аналогичный _binary_data_bin_start, который я переименовал в _val, а _binary_data_bin_size — это «абсолютное» значение. В C это последнее значение можно было бы напечатать с помощью

extern char binary_data_bin_size;
...
printf("%d\n", &binary_data_bin_size);

Однако я даже не понимаю, как здесь может работать указатель, даже если не учитывать предупреждение от компилятора. Это работает, но я не знаю, как и как адаптировать его к gfortran.


person Community    schedule 15.12.2014    source источник
comment
Вы должны прочитать о взаимодействии Fortran с функциями C в документации по набору инструментов. Ручное изменение имен, как и обычные блоки, принадлежит истории.   -  person High Performance Mark    schedule 15.12.2014
comment
@HighPerformanceMark Спасибо за этот ценный комментарий, он приводит меня к частичному решению. Посмотреть изменить   -  person    schedule 15.12.2014
comment
@ Jean-ClaudeArbaut Это очень интересный вопрос. Могу я спросить, где можно найти применение этой технике? Какие проблемы разработки программного обеспечения он решает?   -  person deepak    schedule 18.12.2014
comment
@deepak В моем случае это не является абсолютно обязательным: я использую библиотеки DLL Fortran для использования в Excel, и иногда мне нужно связать некоторые данные (очевидным примером являются номенклатуры, но также и данные для карт и предварительно вычисленных таблиц). Обычно я форматирую все как исходный код на Фортране, но я подумал, что было бы удобно связать файлы напрямую. Теперь я думаю, что я был неправ, но я все еще заинтересован в ответе. Есть и другой вариант: с помощью ресурсов Windows. Я не знаком с этим, но я пробовал на C, и это работает.   -  person    schedule 18.12.2014
comment
@deepak Проще всего, вероятно, оставить все в Excel и использовать VBA для передачи вещей в Fortran, когда это необходимо (но это довольно неудобно с точки зрения кода, а передача строк из VBA не забавна). Тем не менее, это было интересное упражнение. Другой вариант, загрузка данных из исходных файлов, но тогда требуется много файлов в дополнение к DLL. Есть много способов решить мою проблему...   -  person    schedule 18.12.2014


Ответы (1)


Как упоминалось в комментарии, вы хотите использовать встроенный модуль Fortran iso_c_binding для обеспечения функций взаимодействия C.

Следующий код/пример создается с помощью GCC 4.9.2 на x86_64-pc-linux-gnu.

Скалярные данные

Рассмотрим следующий Фортран:

module data
  use iso_c_binding
  implicit none
  real(kind=c_double), bind(C) :: val
end module

program fbin
  use data
  implicit none
   print *,val
end program

Здесь используется модуль Fortran, содержащий переменную var, которая совместима с типом C double. Атрибут bind(C) приведет к тому, что переменная, представленная в объектном файле, будет неискажена (например, var без подчеркивания). Обратите внимание, что использование модуля необходимо, потому что вы не можете применить атрибут привязки в основной области программы (или, по крайней мере, GCC не допустит этого, я не консультировался со стандартом).

Я использовал ваш код для создания data.bin, но для моей системы мне пришлось изменить objcopy следующим образом:

objcopy -I binary -O elf64-x86-64 -B i386 --redefine-sym _binary_data_bin_start=val data.bin data.o

Изменения заключаются в bfdname для создания объекта, который моя система может использовать, и изменении имени символа на простое val, чтобы соответствовать тому, что ожидает компилятор Fortran.

Остальное следует вашему примеру:

% objdump -t data.o                                                                          

data.o:     file format elf64-little

SYMBOL TABLE:
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 g       .data  0000000000000000 val
0000000000000008 g       .data  0000000000000000 _binary_data_bin_end
0000000000000008 g       *ABS*  0000000000000000 _binary_data_bin_size

а также

% gfortran -o fbin fbin.f90 data.o
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/../../../../x86_64-pc-linux-gnu/bin/ld: Warning: alignment 1 of symbol `val' in data.o is smaller than 8 in /tmp/ccYcttlP.o

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

% ./fbin 
   3.1415926535897931     

Успех.

Данные массива

Вот модифицированный build.c для создания одномерного массива значений для тестирования:

#include <math.h>
#include <stdio.h>

int main() {
    FILE *f;
    int i;

    double x; 
    f = fopen("data.bin", "wb");
    for (i=0; i<15; i++) {
      x = M_PI*i;
      fwrite(&(x), sizeof x, 1, f);
    }
    fclose(f);
    return 0;
}

Это создает data.bin с 15 8-байтовыми значениями от 0 до 14*M_PI. data.o создается той же командой, что и в моем скалярном примере.

Вот пример программы на Фортране, жестко закодированной для загрузки массива ранга 2 с формой (3,5).

module data
  use iso_c_binding
  implicit none
  integer, parameter :: n = 3
  integer, parameter :: m = 5
  real(kind=c_double), target, bind(C) :: val
  real(kind=c_double), dimension(:,:), pointer :: array

contains

  subroutine init_fort_array()
    use iso_c_binding
    implicit none
    call c_f_pointer(c_loc(val), array, [n,m])
  end subroutine
end module

program forbin
  use data
  implicit none
  integer :: j
  call init_fort_array()
  print '(5(f8.5,2x))',(array(1:n,j), j=1,m)
end program

Это немного менее просто и, возможно, не единственный способ добиться этого, но именно он пришел на ум. Модуль имеет скалярное значение val и указатель массива. Подпрограмма получает адрес val с c_loc и связывает указатель Фортрана array с этим адресом и использованием данной формы. Вам просто нужно вызвать эту подпрограмму в своем коде, и тогда array заработает, как и ожидалось.

Этот массив жестко закодирован, но вы можете использовать другие файлы данных (или один файл данных с несколькими символами), чтобы упаковать ранг массива и размеры в объект данных, а затем изменить Фортран, чтобы использовать форму, зависящую от фактических данных.

Ни один пример не будет полным без вывода:

% ./fbin                          
 0.00000   9.42478  18.84956  28.27433  37.69911
 3.14159  12.56637  21.99115  31.41593  40.84070
 6.28319  15.70796  25.13274  34.55752  43.98230

Обратите внимание, что Fortran и C не используют один и тот же порядок массивов. C — это строки, а Fortran — столбцы, и вы можете увидеть это в том, как мой цикл Fortran печатает данные.

person casey    schedule 15.12.2014
comment
Благодарю вас! Меня очень интересует решение для массива ;-) Что касается выравнивания, возможно, можно использовать параметры --file-alignment или --section-alignment objcopy, я не пробовал. - person ; 15.12.2014
comment
@Jean-ClaudeArbaut Я добавил пример массива. Я также пробовал параметры выравнивания, которые вы упомянули, но предупреждение компилятора сохраняется для меня. Есть на что посмотреть точно, но пример работает в любом случае. - person casey; 15.12.2014