もう少し本格的に Fortran と C++ の相互運用を試してみる。
Fortran に FEMDATA構造体とFEMFILE構造体を作る。FEMDATA構造体は、有限要素データを意識してidと頂点(x,y,z)と応力(qx,qy,qz)のデータを持つ。この頂点データを、FEMFILE構造体にまとめて持つことにする。FEMDATA構造体の最大数は固定長にしておいて、別途 count を持って実際の頂点数を決める。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | ! 構造体定義 module FEMMODULE implicit none type FEMDATA integer :: id ! idenitry code double precision :: x,y,z ! position double precision :: qx,qy,qz ! potision stress end type FEMDATA type FEMFILE character*10 filename ! file name character*10 author ! author name character*10 makedate ! make datetime 'yyyy/mm/dd' integer :: count ! data count type(FEMDATA) dat(1000) ! data end type FEMFILE type(FEMFILE) fdata ! inner data contains ! read data file subroutine FEMREAD( fname ) character(*) :: fname character*80 :: temp integer :: i, count open(10,file=fname,status= 'old') read(10,*) temp, fdata%author read(10,*) temp, fdata%makedate read(10,*) temp, fdata%count count = fdata%count print *, "count:" , fdata%author, fdata%makedate, count do i=1,count read(10,*) temp, & & fdata%dat(i)%id, & & fdata%dat(i)%x, & & fdata%dat(i)%y, & & fdata%dat(i)%z, & & fdata%dat(i)%qx,& & fdata%dat(i)%qy,& & fdata%dat(i)%qz end do close(10) end subroutine FEMREAD ! write data file subroutine FEMWRITE( fname ) character(*) :: fname integer :: i, count fdata%filename = fname open(10,file=fname,status= 'replace') write(10,*) "author " , fdata%author write(10,*) "makedate " , fdata%makedate count = fdata%count write(10,*) "count " , fdata%count do i=1,count write(10, "(A,I5,6E15.7)" ) "data" , & & fdata%dat(i)%id, & & fdata%dat(i)%x, & & fdata%dat(i)%y, & & fdata%dat(i)%z, & & fdata%dat(i)%qx,& & fdata%dat(i)%qy,& & fdata%dat(i)%qz end do close(10) end subroutine FEMWRITE ! set dummy data subroutine FEMDUMMY() integer :: i fdata%filename = "sample.txt" fdata%author = "t.masuda" fdata%makedate = "2012-05-02" fdata%count = 100 do i=1,100 fdata%dat(i)%id = i fdata%dat(i)%x = 1.0 fdata%dat(i)%y = 1.0 fdata%dat(i)%z = 1.0 fdata%dat(i)%qx = 0.1 fdata%dat(i)%qy = 0.1 fdata%dat(i)%qz = 0.1 end do end subroutine FEMDUMMY end module FEMMODULE |
データをファイルに書き出すのがFEMWRITE関数で、同じファイルから読み込むのがFEMREAD関数。いわゆる永続化処理。Fortranで書き出しファイルはFortran自身で読み出すのが良いのと、配列の実体はFortran上にあるのでこれをアクセスするのは、Fortran自身で行うのが良い、という主旨です。
で、データをGUIを使って突っ込むところはC++(MFC)でやったほうが良かろうということです。最近のIntel Fortran では windowsアプリケーションを作ることもできるようなのですが、まぁ、C++で作ったほうが便利。もっと云えば、C++/CLIを通して、C#を使うのもありかと。ただし、C#で作る場合は、相互運用部分のクラスを相当考え抜かないと解析スピードに難が出そうな気がします。
出力されるデータは、以下のような形です。C言語でも読み込めないことはないけど、コードを見て分かる通り Fortran の場合は、read(*,*) でさっくりと読み込めます。
1 2 3 4 5 6 7 8 | author t.masuda makedate 2012-05-02 count 100 data 1 0.1000000E+01 0.1000000E+01 0.1000000E+01 0.1000000E+00 0.1000000E+00 0.1000000E+00 data 2 0.1000000E+01 0.1000000E+01 0.1000000E+01 0.1000000E+00 0.1000000E+00 0.1000000E+00 data 3 0.1000000E+01 0.1000000E+01 0.1000000E+01 0.1000000E+00 0.1000000E+00 0.1000000E+00 data 4 0.1000000E+01 0.1000000E+01 0.1000000E+01 0.1000000E+00 0.1000000E+00 0.1000000E+00 ... 以下 100 まで続く |
構造体自体が入れ子になっているので、以下のように「%」が二回続く(C言語で言う「.」です)のがいやらしいところですね。Fortranでポインタを使っても良いけど…C++の「参照(&)」に当たるものってあるんでしょうか?
1 2 3 4 5 6 7 8 9 10 | do i=1,count read(10,*) temp, & & fdata%dat(i)%id, & & fdata%dat(i)%x, & & fdata%dat(i)%y, & & fdata%dat(i)%z, & & fdata%dat(i)%qx,& & fdata%dat(i)%qy,& & fdata%dat(i)%qz end do |
これを C++ から呼び出すときは以下のコードで。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | extern "C" { void FEMMODULE_mp_FEMDUMMY( void ); void FEMMODULE_mp_FEMWRITE( const char *); } // 構造体の再定義 struct FEMDATA { int id ; double x,y,z; double qx,qy,qz; }; struct FEMFILE { char filename[10]; char author[10]; char makedate[10] ; int count ; FEMDATA dat[1000]; }; extern "C" FEMFILE FEMMODULE_mp_FDATA; int _tmain( int argc, _TCHAR* argv[]) { cout << "call fortran struct" << endl; FEMMODULE_mp_FEMDUMMY(); FEMMODULE_mp_FEMWRITE( "sample03.txt" ); FEMFILE &dat = FEMMODULE_mp_FDATA ; cout << "filename: " << FEMMODULE_mp_FDATA.filename << endl; cout << "count: " << dat.count << endl ; return 0; } |
実は、このコードは意図的にバグがあって、filenameとかauthorとかは null 終端ではないのですよ。なので、単純に、FEMMODULE_mp_FDATA.filename を出力すると、10文字以下の文字もずらずらと表示されてしまいます。なので、この部分を string に変えたいですね。Fortran側で、NULL 分の char を入れておくという方法もあるのですが、以下な構造体で扱いたい。
1 2 3 4 5 6 | struct FEMFILEX { string filename ; string author ; string makedate ; vector<FEMDATA*> dat; }; |
文字列のところは string に変換して、配列のところは vector に直します。カウンターはいらないので vector::size を参照ということにしたい。欲を言えば、これを C++ のクラスにしたいですね。
Fortran の構造体と C++ のクラスを相互運用させる時、自前でちまちま書けばできないことはないのでしょうが、それだと可搬性が悪くなるし、たくさんのFortran構造体を扱ったときに、それだけで作業量が膨大になってしまう。なので、実務的にFortarn/C の相互運用を考える場合、
- Fortran構造体をC構造体に逐一直す作業
- C構造体をstring/vectorを使ったC++構造体に直す作業
を考えないといけない。これを template か適当なスクリプトでやろうかなと。1のほうはperlスクリプトかマクロを使って手作業、2のほうはtemplateを使えると良いかなと、思案中。