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

Мой главный вопрос заключается в том, почему массивы делают такие странные вещи и есть ли вообще способ сделать следующее «чистым» способом.

В настоящее время у меня есть программа C foo.c, взаимодействующая с программой Fortran 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 (работа с этими данными приводит к segfaults). Я также пробовал это для массивов фиксированного размера - результаты остаются прежними.

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

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

Изменить:

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

Изменить:

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


person Sty    schedule 13.08.2012    source источник
comment
Вам определенно следует использовать ISO_C_BINDING, на этом сайте есть много вопросов и ответов по этому поводу.   -  person Vladimir F    schedule 13.08.2012
comment
Обычно вы не передаете массивы с помощью C_LOC и C_PTR (если да, то используйте VALUE!), а напрямую (Fortran передает по ссылке для процедур bind(C)).   -  person Vladimir F    schedule 13.08.2012


Ответы (2)


На мой взгляд, не рекомендуется пытаться получить доступ к глобальным данным в библиотеке Fortran. Это можно сделать с помощью блоков COMMON, но они вредны и требуют массивов статического размера. Как правило, ассоциация хранения - это плохо.

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

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

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

бар.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 

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

person Vladimir F    schedule 13.08.2012
comment
Глобальные данные могут быть разделены между Fortran и C с использованием переменных модуля и привязки ISO C. - person M. S. B.; 13.08.2012
comment
Да, я некоторое время думал об этом решении. Первый абзац вводит в заблуждение, потому что может предположить, что ОБЩИЙ путь — единственный. Обычно я вообще не люблю делиться глобальными данными и предпочел бы, чтобы allocData не выделял глобальный массив, а выделял массив указателей и возвращал указатель c на него. Затем программа C передаст указатель на подпрограммы. - person Vladimir F; 13.08.2012
comment
Вы обычно не заботитесь о символах Fortran. Вы должны использовать iso_C_binding, и тогда каждая совместимая функция или переменная с объявленной вами привязкой получит запрошенный вами символ. Я выступаю за то, чтобы не привязывать глобальные символы, а передавать указатели (c_ptr) или использовать фиктивные аргументы. Доступ к искаженным именам символов Fortran почти всегда плохая идея. - person Vladimir F; 14.08.2012

Многие компиляторы Фортрана используют внутри так называемые дескрипторы массива - структуры, которые содержат форму массива (то есть размер и диапазон каждого измерения, а также указатель на реальные данные). Он позволяет реализовать такие вещи, как аргументы массива предполагаемой формы, указатели массива и размещаемые массивы. То, что вы получаете через символ __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
person Hristo Iliev    schedule 13.08.2012
comment
Вы определенно должны использовать ISO_C_BINDING. - person Vladimir F; 13.08.2012
comment
Ну а если поставить на то, что проект спецификации N1904 на дескриптор массива рано или поздно будет принят, то можно получить доступ к данным через указатель в начале. В противном случае вам лучше определить подпрограммы/функции доступа. - person Hristo Iliev; 13.08.2012
comment
@HighPerformanceMark, правильно. Я переформулировал абзац. Также в примечании 15.16 стандарта ISO/IEC 1539-1:2010 прямо указано, что выделяемые массивы несовместимы с C. Тем не менее, дальнейшее взаимодействие Fortran с C N1917 пытается раскрыть эту деталь реализации, чтобы обеспечить обратную совместимость (например, доступ к объектам Fortran из C). - person Hristo Iliev; 13.08.2012