Я пытаюсь использовать код Fortran из приложения C ++. В частности, я пытаюсь взаимодействовать с SLATEC drc3jj.f. Однако подпрограмма Fortran возвращает массив, размер которого зависит от параметров, передаваемых в функцию.
Если размер массива равен 1, то выводимый мной массив C ++ содержит соответствующее значение. Однако, если этот размер больше единицы, массив C ++ содержит NaN, где должны быть выходные значения.
Ниже приведен код, который я использую. Это просто связывает подпрограмму Fortran с приложением C ++.
#ifndef FORTRANLINKAGE_H
#define FORTRANLINKAGE_H
extern "C"{
extern void drc3jj_(double*,double*,double*,double*,double*,
double*,double [],int*,int*);
}
#endif // FORTRANLINKAGE_H
Мясо здесь внизу. Мы фактически вызываем подпрограмму Fortran из C ++ и выводим вывод:
#include "fortranLinkage.h"
#include <iostream>
#include <stdlib.h>
using namespace std;
void wigner3j(double l2, double l3, double m2, double m3, double coeff [])
{
double l1min,l1max;
int ierr,size(3);
drc3jj_(&l2,&l3,&m2,&m3,&l1min,&l1max,coeff,&size,&ierr);
cout << "Min: " << l1min << "\t Max: " << l1max << "\t Err: " << ierr << endl;
}
int main(int argc, char const *argv[])
{
int l1(atoi(argv[1])),l2(atoi(argv[2])),m2(atoi(argv[3])),m3(atoi(argv[4]));
double coeff [3];
wigner3j(l1,l2,m2,m3,coeff);
for (int i=0;i<3;i++)
{
cout << coeff[i] << endl;
}
return 0;
}
Если мы вызываем программу с ./myProgram 2 8 2 8
, он правильно выводит 1 / sqrt (21). Однако, если мы попробуем ./myProgram 2 8 2 7
где размер массива фактически равен 2, мы получаем такой результат:
Min: 9 Max: 10 Err: 0
-nan
-nan
2.08175e-317
На самом деле NaN имеют правильный знак.
В любом случае, есть ли другой (правильный) способ передачи массивов C ++ в Fortran? Это вообще проблема?
Проблема заключается не в интерфейсе между C ++ и Fortran, а скорее в устаревшей реализации Fortran. Файл drc3jj.f является частью библиотеки SLATEC, в которой есть служебные функции, которые возвращают константы, которые зависят от машины, на которой он работает (машинные константы). Они определены в файлах d1mach.f
, i1mach.f
а также r1mach.f
,
Тем не менее, начиная с Фортрана 95, существуют такие особенности, как huge()
, tiny()
, spacing()
а также epsilon()
которые гарантированно вернут правильные значения для любой машины.
Решение, таким образом, состоит в том, чтобы удалить любую ссылку на d1mach(int)
в drc3jj()
подпрограммы и заменить их на соответствующие встроенные функции.
Более того, прямая ссылка на подпрограммы на Фортране всегда может быть хитрой, поскольку она зависит от компилятора; лучше использовать iso_c_binding
в fortran90 определить интерфейс к C для вас безопасным способом:
!wrapper.f90:
subroutine drc3jj_wrap(l2, l3, m2, m3, l1min, l1max, thrcof, ndim, ier) bind(C)
use iso_c_binding
implicit none
real(c_double), value, intent(in) :: l2, l3, m2, m3
real(c_double), intent(out) :: l1min, l1max
real(c_double), dimension(ndim), intent(out):: thrcof
integer (c_int), value, intent(in) :: ndim
integer (c_int), intent(out) :: ier
interface
SUBROUTINE DRC3JJ (L2, L3, M2, M3, L1MIN, L1MAX, THRCOF, NDIM, IER)
INTEGER NDIM, IER
DOUBLE PRECISION L2, L3, M2, M3, L1MIN, L1MAX, THRCOF(NDIM)
end SUBROUTINE DRC3JJ
end interface
call DRC3JJ(l2, l3, m2, m3, l1min, l1max, thrcof, ndim, ier)
end subroutine drc3jj_wrap
а также
// fortranLinkage2.h
#ifndef FORTRANLINKAGE_H
#define FORTRANLINKAGE_H
extern "C"{
extern void drc3jj_wrap(double l2, double l3, double m2, double m3,
double *l1max, double *l2max, double *thrcof,
int ndim, int *ier);
}
#endif // FORTRANLINKAGE_H
а потом просто позвони
void wigner3j(double l2, double l3, double m2, double m3, double coeff [])
{
double l1min,l1max;
int ierr,size(3);
drc3jj_wrap(l2,l3,m2,m3,&l1min,&l1max,coeff,size,&ierr);
cout << "Min: " << l1min << "\t Max: " << l1max << "\t Err: " << ierr << endl;
}
и компиляция и запуск дает
$ g++ -c foo2.cc
$ gfortran -c wrapper.f90
$ gfortran -c drc3jj.f
$ g++ -o foo2 foo2.o wrapper.o drc3jj.o -lgfortran
$ ./foo2 2 8 2 8
Min: 10 Max: 10 Err: 0
0.218218
2.07738e-317
0
gpc-f103n084-$ ./foo2 2 8 2 7
Min: 9 Max: 10 Err: 0
-0.102598
-0.19518
0
Других решений пока нет …