Передача скаляров и массивов (любых размеров) из Фортрана в Си

У меня есть следующая подпрограмма Fortran с именем show_value, которая вызывает функцию C с именем show_value:

INTERFACE
    SUBROUTINE show_value(variable) BIND(C, name = "show_value")
        USE, INTRINSIC :: iso_c_binding
        TYPE(*) :: variable
    END SUBROUTINE
END INTERFACE

Функция C show_value:

void show_value(const void *variable)
{
    printf("%d\n", *(int *) variable);
}

Подпрограмма Fortran хорошо работает при передаче ей скаляров. Пример:

INTEGER :: x
x = 12
call show_value(x)

Это вызовет функцию C show_value и напечатает 12, что правильно.

Теперь, согласно документации Фортрана, если вы хотите, чтобы подпрограмма show_value принимала также массивы (любой размерности), а не только скаляры, строку TYPE(*) :: variable следует заменить на TYPE(*), DIMENSION(..) :: variable.

После внесения этого изменения при выполнении следующего кода Fortran:

INTEGER, DIMENSION(3) :: y
y(1) = 15
y(2) = 17
y(3) = 19
call show_value(y)

Функция C show_value больше не печатает правильное сообщение (т.е. печатает случайные числа). Более того, я обнаружил, что адрес, который получает функция Си, на 528 ниже исходного (на Фортране). Чтобы подтвердить это:

void show_value(const void *variable)
{
    printf("%d\n", *(int *) (variable + 528));
}

... который печатает 15 (правильное число).

Есть идеи, что здесь происходит?

Среда: Ubuntu 14.04 64 бит, gfortran 4.9


person user7698505    schedule 10.07.2018    source источник
comment
Исправлено, спасибо, что указали на это!   -  person user7698505    schedule 10.07.2018


Ответы (1)


Хотя ваш первый случай со скалярным аргументом работает корректно, сопоставляя аргумент void*, когда аргумент вызова имеет предполагаемый ранг (который представляет type(*), dimension(..) :: variable), процедура Fortran не может взаимодействовать с процедурой C с соответствующим формальным аргументом const void *variable.

Вместо этого необходимо использовать механизм CFI_cdesc_t:

#include <stdio.h>
#include <ISO_Fortran_binding.h>

void show_value(const CFI_cdesc_t* variable)
{
  printf("%d\n", *(int*) variable->base_addr);
}

Вы можете найти подробности об этом в Fortran 2018 18.5.3.

По существу, однако, это дескриптор, который имеет большую часть деталей объекта Fortran. Здесь base_addr — это начало данных, но вы также найдете статус размещения/указателя/данных, ранг, экстенты, тип.

Увы, gfortran 4.9 этого не поддерживает. Если он вообще будет поддерживаться, то только в самых последних версиях.


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

use, intrinsic :: iso_c_binding, only : c_loc, c_ptr, c_int
interface
  subroutine show_value(variable) bind(c)
    import c_ptr
    type(c_ptr), value :: variable
  end subroutine
end interface

integer(c_int), target :: x, y(3)

x = 12
y = [15, 17, 19]

call show_value(c_loc(x))
call show_value(c_loc(y))

end

Это, однако, оставляет проблему того, как функция C узнает, что делать с аргументом.

person francescalus    schedule 10.07.2018
comment
Спасибо за ваш отзыв. Не могли бы вы привести пример использования c_loc? Это было бы прекрасно! - person user7698505; 10.07.2018
comment
Конечно, у вас есть (непереносимая) возможность попытаться понять передаваемый дескриптор массива gfortran (в данном случае это первые 528 байт). Даже если бы я мог помочь там, я бы категорически не советовал этого делать. - person francescalus; 10.07.2018
comment
Проверьте это для получения дополнительной информации о статус этой функции в gfortran. Кроме того, если вы хотите углубиться в дескриптор массива gfortran, взгляните на это . - person Rodrigo Rodrigues; 10.07.2018