Как получить доступ к динамически размещаемым массивам Fortran в C

Мой главный вопрос: почему массивы делают такие странные вещи, и есть ли какой-нибудь способ сделать следующее "чистым" способом?

У меня в настоящее время есть программа C foo.c взаимодействие с программой на Фортране bar.f90 с помощью dlopen/dlsym примерно как в коде ниже:

foo.c:

#include <dlfcn.h>
#include <stdio.h>

int main()
{
int i, k = 4;
double arr[k];
char * e;

void * bar = dlopen("Code/Test/bar.so", RTLD_NOW | RTLD_LOCAL);

void (*allocArray)(int*);
*(void **)(&allocArray) = dlsym(bar, "__bar_MOD_allocarray");
void (*fillArray)(double*);
*(void **)(&fillArray) = dlsym(bar, "__bar_MOD_fillarray");
void (*printArray)(void);
*(void **)(&printArray) = dlsym(bar, "__bar_MOD_printarray");
double *a = (double*)dlsym(bar, "__bar_MOD_a");

for(i = 0; i < k; i++)
    arr[i] = i * 3.14;

(*allocArray)(&k);
(*fillArray)(arr);
(*printArray)();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
printf("\n");

return 0;
}

bar.f90:

module bar

integer, parameter :: pa = selected_real_kind(15, 307)
real(pa), dimension(:), allocatable :: a
integer :: as

contains

subroutine allocArray(asize)
    integer, intent(in) :: asize

    as = asize
    allocate(a(asize))

    return
end subroutine

subroutine fillArray(values)
    real(pa), dimension(as), intent(in) :: values

    a = values
    return
end subroutine

subroutine printArray()
    write(*,*) a
    return
end subroutine

end module

Запуск основных урожаев

0.0000000000000000        3.1400000000000001        6.2800000000000002        9.4199999999999999     
0.000000 -nan 0.000000 0.000000 

что показывает, что Fortran правильно распределяет массив и даже правильно хранит заданные значения, но они больше не доступны через dlsym (работа с этими данными приводит к ошибкам сегмента). Я также попробовал это для массивов фиксированного размера - результаты остались прежними.

Кто-нибудь знает причину такого поведения? Лично я ожидал, что что-то получится либо двунаправленным, либо, альтернативно, совсем нет - этот "Фортран принимает массивы С, но не наоборот" заставляет меня задуматься, не совершил ли я какую-то основную ошибку при доступе к массиву из С таким способом.

Другой (и даже более важный) вопрос заключается в том, как сделать доступ к массиву таким "правильным способом". В настоящее время я даже не уверен, что придерживаться интерфейса "Fortran as .so" - это вообще хороший способ - я думаю, что в этом случае также можно было бы попробовать смешанное программирование. Тем не менее, проблема с массивами остается - я читал, что это может быть каким-то образом решено с помощью привязки ISO C, но я пока не мог понять, как (пока я не очень много работал с Fortran, особенно с упомянутой Binding)., поэтому помощь по этому вопросу будет принята с благодарностью.

Редактировать:

Итак, я немного подробнее прочитал о привязке ISO C и нашел здесь весьма полезный подход. С помощью C_LOC Я могу получить C-указатели на мои структуры Fortran. К сожалению, указатели на массивы, кажется, являются указателями на указатели и должны быть разыменованы в коде C, прежде чем их можно будет рассматривать как массивы C - или что-то в этом роде.

Редактировать:

Теперь моя программа работает, используя привязку C, как указал Владимир F, по крайней мере, по большей части. Файл C и файлы Fortran теперь связаны друг с другом, так что я могу избежать интерфейса libdl, по крайней мере, для части Fortran - мне все еще нужно загрузить динамическую библиотеку C, получить указатель функции на один из символов и передать его как указатель на функцию Fortran, которая позже вызывает эту функцию как часть своего вычисления. Поскольку указанная функция ожидает двойные * s [массивы], мне не удалось передать мои массивы Fortran с помощью C_LOC, как ни странно - ни C_LOC(array) ни C_LOC(array(1)) передал правильные указатели обратно в функцию C. array(1) сделал трюк, хотя. К сожалению, это не самый "чистый" способ сделать это. Если у меня есть подсказка, как это сделать, используя C_LOC функция, это было бы здорово. Тем не менее я принимаю ответ Владимира Ф., так как считаю его более безопасным решением.

2 ответа

Решение

На мой взгляд, не стоит пытаться получить доступ к глобальным данным в библиотеке Фортрана. Это может быть сделано с использованием ОБЩИХ блоков, но они злые и требуют массивов статического размера. Вообще ассоциация хранения - это плохо.

Никогда не обращайтесь к символам модуля как "__bar_MOD_a", они зависят от компилятора и не предназначены для непосредственного использования. Передача пуазеров с использованием функций и подпрограмм.

Передайте массив в качестве аргумента подпрограммы. Вы также можете выделить массив в C и передать его в Fortran. Также можно получить указатель на первый элемент массива. Он будет служить указателем C на массив.

Мое решение, для простоты без.so, добавить его тривиально:

bar.f90

module bar
 use iso_C_binding

implicit none

integer, parameter :: pa = selected_real_kind(15, 307)

real(pa), dimension(:), allocatable,target :: a
integer :: as

contains

subroutine allocArray(asize,ptr) bind(C,name="allocArray")
    integer, intent(in) :: asize
    type(c_ptr),intent(out) :: ptr

    as = asize
    allocate(a(asize))

    ptr = c_loc(a(1))
end subroutine

subroutine fillArray(values) bind(C,name="fillArray")
    real(pa), dimension(as), intent(in) :: values

    a = values
end subroutine

subroutine printArray()  bind(C,name="printArray")

    write(*,*) a
end subroutine

end module

main.c

#include <dlfcn.h>
#include <stdio.h>

int main()
{
int i, k = 4;
double arr[k];
char * e;
double *a;
void allocArray(int*,double**);
void fillArray(double*);
void allocArray();


for(i = 0; i < k; i++)
    arr[i] = i * 3.14;

allocArray(&k,&a);
fillArray(arr);
printArray();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
printf("\n");

return 0;
}

скомпилируйте и запустите:

gcc -c -g main.c

gfortran -c -g -fcheck=all bar.f90

gfortran main.o bar.o

./a.out
0.0000000000000000        3.1400000000000001        6.2800000000000002        9.4199999999999999     
0.000000 3.140000 6.280000 9.420000 

Примечание. Нет никаких причин для возвратов в ваших подпрограммах на Фортране, они только затеняют код.

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

Дескрипторы массива зависят от компилятора, а код, основанный на конкретном формате дескриптора, не является переносимым. Пример дескрипторов:

Обратите внимание, что даже они специфичны для некоторых версий этих компиляторов. Intel, например, заявляет, что их текущий формат дескриптора не совместим с форматом, используемым в Intel Fortran 7.0.

Если вы посмотрите на оба дескриптора, вы увидите, что они в большей степени похожи и что первый элемент является указателем на данные массива. Таким образом, вы сможете легко прочитать данные, используя double ** вместо double *:

double **a_descr = (double**)dlsym(bar, "__bar_MOD_a");
...
for(i = 0; i < 4; i++)
    printf("%f ", (*a_descr)[i]);

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

Изменить: вот как использовать функцию доступа, которая использует C_LOC() от ISO_C_BINDING модуль для переноса получения указателя на размещаемый массив:

Код Фортрана:

module bar
  use iso_c_binding
  ...
  ! Note that the array should be a pointer target
  real(pa), dimension(:), allocatable, target :: a
  ...
contains
  ...

  function getArrayPtr() result(cptr)
    type(c_ptr) :: cptr

    cptr = c_loc(a)
  end function

end module

Код C:

...
void * (*getArrayPtr)(void);
*(void **)(&getArrayPtr) = dlsym(bar, "__bar_MOD_getarrayptr");
...
double *a = (*getArrayPtr)();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
...

Результат:

$ ./prog.x
   0.0000000000000000        3.1400000000000001        6.2800000000000002
 9.4199999999999999
0.000000 3.140000 6.280000 9.420000
Другие вопросы по тегам