今日のメニュー:(和洋折衷): Fortran & C++

これは、2000/06/26 の MkLinux/LinuxPPC な生活に書いたものである。Fortran と C++ と言うと和洋折衷というより、古代料理と現代グルメ料理の組み合わせと言った方が適切かもしれない。いずれにせよ、Fortran の遺産はまだまだ大きいのでこれを活用しようと言うわけである。実際、縄文食は健康的だがグルメは体に悪いからというわけでもなかろうが、Fortran にこだわり続ける人がこの業界にも結構いる。

Fortran から C++ の関数を呼ぶ

実際、BASES/SPRING のヒストグラムパッケージが C++ になってしまったので、これが必須。以下に例を示す。
FORTRAN は例えば以下のようにヒストグラムをフィルするサブルーチン xhfill を呼ぶ。

      subrouitine func(x)
      implicit real*8 (a-h,o-z)
      external xhfill
      .........
      call xhfill('h01', x, w)
      .........
      return
ここで、x はフィルする値、w は重みである。この xhfill の実体を C++ で書かれた JSFBases クラスのメンバー関数(JSFBases::H1Fill)にしたいわけである。
この場合、まず FORTRAN 側の修正は、上のように external xhfill を宣言すること。次に、C++ の側で以下のようにする。
JSFBases *bso;   // これは H1Fill をメンバー関数とする JSFBases オブジェクトへのポインター
extern "C" {     // これでシンボル名がマングルされないので、FORTRAN から見える
void xhfill_(char *t, double *x, double *w, int len) // xhfill の後のアンダースコアー、
{                                                    // 文字列の長さを保持する引数 len に注意。
   char tmp[1024];                                   // また、引数は全てアドレスわたしである
   int i;
   for (i=0; i<len; i++) tmp[i] = t[i];
   tmp[len] = '\0';          // FORTRAN の文字列はヌルターミネートされていない。
   bso->H1Fill(tmp,*x,*w);  // 実行時、bso は確定しているのでメンバー関数が呼び出せる。
}
}
FORTRAN 関数名にはアンダースコアーがつけられること、引数が全て値でなくアドレスわたしであること、そして FORTRAN の文字列の扱いの特殊性に注意(FORTRAN では文字列はヌルターミネートされておらず、そのかわりに文字列の長さを、FORTRAN では見えない引数でわたす。従って C または C++ にその文字列をわたす際にはヌルターミネートしてから、その長さといっしょにわたす必要がある)。
後は、bso を JSFBases のコンストラクターの中で
jSFBases::JSFBases(......) : .....
{
   .....
   bso = this;
   .....
}

のようにセットする。スタティック関数なら bso->H1Fill(tmp,*x,*w) のようにポインターを使うのでなく、JSFBases::H1Fill(tmp,*x,*w) のように書けばよい。

C++ から Fortran の関数を呼ぶ

これは、上の場合のノウハウの応用である。例えば

      subroutine subfort(i,x,str1,array,str2)
      integer*4 i
      real*4    x
      character*(*) str1, str2
      ......
      return
      end
なる FORTRAN 関数は、
extern "C" {
extern void subfort_(int *i, float *x, char *str1, float array[],
                     char *str2, int len1, int len2);
......
}
などとしておけば良い。もちろん、これを C++ から呼ぶ際にはアドレスわたしであることに注意し、len1 と len2 もセットして呼ばねばならない。
例えば、
int i = 0;
float x =1.;
float array[] = {0., 1., 2.};
char *str1 = ".....";
char *str2 = ".....";
......
subfort_(&i, &x, str1, array, str2, strlen(str1), strlen(str2));

のような感じ。

C++ から Fortran の COMMON にアクセスする

例えば、次のような FORTRAN の COMMON にアクセスしたい場合。

      common /fcom/ i, x, y, a(100)
      integer*4  i
      real*4     x, y, a
まず
extern "C" {
  typedef struct {
     int   i;
     float x;
     float y;
     float a[100];
  } COMMON_fcom;
}
などと宣言し、
extern COMMON_fcom fcom_;
後は、C++ のプログラムの中で
fcom_.i = 1;
fcom_.a[10] = 0.5;
float x = fcom_.x;
などとすればよい。これで、FORTRAN の配列要素:a(11) が変更される。FORTRAN 配列は1から始まる点に注意。