Как получить доступ к динамически размещаемым массивам 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