Fortranで複雑な構造体にファイルをロードする

もう少し本格的に Fortran と C++ の相互運用を試してみる。

Fortran に FEMDATA構造体とFEMFILE構造体を作る。FEMDATA構造体は、有限要素データを意識してidと頂点(x,y,z)と応力(qx,qy,qz)のデータを持つ。この頂点データを、FEMFILE構造体にまとめて持つことにする。FEMDATA構造体の最大数は固定長にしておいて、別途 count を持って実際の頂点数を決める。

! 構造体定義
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(*,*) でさっくりと読み込めます。

 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++の「参照(&)」に当たるものってあるんでしょうか?

        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++ から呼び出すときは以下のコードで。

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 << &quot;call fortran struct&quot; << endl;

	FEMMODULE_mp_FEMDUMMY();
	FEMMODULE_mp_FEMWRITE(&quot;sample03.txt&quot;);

	FEMFILE &dat = FEMMODULE_mp_FDATA ;
	cout << &quot;filename: &quot; << FEMMODULE_mp_FDATA.filename << endl;
	cout << &quot;count: &quot; << dat.count << endl ;
	return 0;
}

実は、このコードは意図的にバグがあって、filenameとかauthorとかは null 終端ではないのですよ。なので、単純に、FEMMODULE_mp_FDATA.filename を出力すると、10文字以下の文字もずらずらと表示されてしまいます。なので、この部分を string に変えたいですね。Fortran側で、NULL 分の char を入れておくという方法もあるのですが、以下な構造体で扱いたい。

struct FEMFILEX {
	string filename ;
	string author ;
	string makedate ;
	vector<FEMDATA*> dat;
};

文字列のところは string に変換して、配列のところは vector に直します。カウンターはいらないので vector::size を参照ということにしたい。欲を言えば、これを C++ のクラスにしたいですね。

Fortran の構造体と C++ のクラスを相互運用させる時、自前でちまちま書けばできないことはないのでしょうが、それだと可搬性が悪くなるし、たくさんのFortran構造体を扱ったときに、それだけで作業量が膨大になってしまう。なので、実務的にFortarn/C の相互運用を考える場合、

  1. Fortran構造体をC構造体に逐一直す作業
  2. C構造体をstring/vectorを使ったC++構造体に直す作業

を考えないといけない。これを template か適当なスクリプトでやろうかなと。1のほうはperlスクリプトかマクロを使って手作業、2のほうはtemplateを使えると良いかなと、思案中。

カテゴリー: C++, Fortran | Fortranで複雑な構造体にファイルをロードする はコメントを受け付けていません

FortranとC++のstringクラスでやり取りする

FortranではC言語の文字列(char[])位のやり取りをするついでに、C++のstringクラスとのやり取りも実験的に。
今回、画面側がMFCなので、CStringを使うかstringを使うか悩むところなのですが、まぁSTLをごりごり使うという前提でstd::stringを使ってみます。

module strmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( str )
        character(*) :: str
        print *, "in fstr1:", str
    end subroutine fstr1
    ! 文字列を返す
    subroutine fstr2( str ) 
        character*80 :: str
        str = "masuda tomoaki"
    end subroutine fstr2
end module strmodule

Fortran側の module は、「character(*)」で文字列を受け取ります。逆に文字列を渡す場合には「character*80」のようにサイズを指定します。Fortranだけでで文字列を表示するときはいいのですが、C言語とやり取りする時にはうしろの空白が面倒なのです。Fortranの場合は、空きの分は空白で埋めてくれます。

extern "C" {
	void STRMODULE_mp_FSTR1( const char *str, int length );
	void STRMODULE_mp_FSTR2( char *str, int length );
}
// 文字列を設定
void FSTR1( string &str )
{
	STRMODULE_mp_FSTR1(str.c_str(), str.length());
}
// 文字列を取得
string FSTR2()
{
	static char buf[1000] = {0}; // 大きめのバッファ
	STRMODULE_mp_FSTR2( buf, sizeof(buf));
	string str = buf;
	int e = str.find_last_not_of(" ");
	if ( e != string::npos ) {
		str = string( str,0, e+1 );
	}
	return str;
}

なので、std::stringから、Fortranの関数に渡す時には長さを指定して、逆に受け取る場合には string::find_last_not_of で後ろの空白を削除する(trimする)という定番処理が必要になります。

	// string クラスとの相互運用
	string str = &quot;masuda tomoaki&quot;;
	// そのまま fortran に渡す
	cout << &quot;call FSTR1&quot; << endl;
	FSTR1( str );
	// fortran から受け取る
	str = FSTR2();
	cout << &quot;call FSTR2:[&quot; << str << &quot;]&quot; << endl;

文字列の場合は、この変換が必要なので適当なマクロを作るか、関数を作るかするかなぁと。char[] のまま C++ で扱うのは厄介なので(今回、STLを使ってよいという話なので)、このあたりは適当に隠蔽したいところ。

本来ならば、unicode を気遣う必要があるのですが、今回は英語版なので…まあ良しとしましょう(ってのがi18nの問題でもあるのだけど、既にあるコードが string を使っているからね。Unicode に気を遣う場合は、MFC の時は CString を使うのが良いです)。

カテゴリー: C++, Fortran | FortranとC++のstringクラスでやり取りする はコメントを受け付けていません

Fortranでファイルの読み書きRead/Writeする

Fortran入門: データ入出力
http://www.nag-j.co.jp/fortran/FI_14.html
format文 (Fortranプログラミング入門マニュアル | Fortran プログラミング 入門 講座)
http://harukin.la.coocan.jp/fortran/004/format.html

Fortranのファイル読み書き&フォーマット文に関しては上記を参照。
ただし、Fortran77 の時もそうだったのだが、Fortran で書き出して、Fortran で読み込む分にはさほど書式に気を遣わなくていい…というか、適当に write(*,*) で書き出して、read(*,*) で読み込めるってのが Fortran の便利なところ。

program Console1
    implicit none
    integer count
    integer readdata
    character*80 fname

    ! ファイル出力
    open(10,file='sample.txt',status='replace')
    write (10,*) 'Hello', x, y 
    close(10)
    print *, 'write sample.txt'

	! 一応初期化    
    x = 0 
    y = 0

    ! ファイル入力
    open(11,file='sample.txt',status='old')
    read(11,*), str, x, y
    close(11)
    
    print *, 'read sample.txt'
    print *, 'read:', str, x, y
    
    fname = 'sample2.txt'
    count = readdata(fname)
    print *,'read count:', count 
end program Console1

フォーマットを決めずに write した後に read する例。単純に、変数を並べて write すれば良しなにしてくれるので非常に便利です。固定のデータ数や配列の場合には、ちまちま書くか、do ループを使って固定数分読み書きすればよい。

    ! データを終端 "END" まで読み込む
    integer function readdata(fname)
        implicit none
        ! character*80 :: fname
        character(*), intent(in) :: fname
        integer :: count
        character*20 :: mark
        integer :: x, y
        count = 0
        
        print *,'fname:', fname
        open(10,file=fname,status='old')
        do 
            read(10,*,end=100), mark, x, y
            count = count + 1
            if ( mark == 'end' ) then
                exit
            end if
            print *, 'read:',x,y
        end do
100     close(11)        
        readdata = count
    end function readdata

この do ループの部分は定番です。read の時に end=100 のように行番号で飛ばすのは、Fortran90 のスタイルではどうなのだろう?と思うのですが、ひとまずこれで。ファイル番号を付けるのは、VB6 と同じです。適当にダブらない番号を付ければ OK です。ただし、サブルーチンからサブルーチンを呼び出すときに、このファイル番号がダブった場合どうなるのかは不明。なので、open/close 自体はちょっと注意しないと。

このサンプルでは、文字列の先頭が「end」で終わったときに終端と判断していますが、数値が「-1」の時というのも定番な動作です。C++の場合は、1行読み込んだ後に、先頭が「end」なのか、有効な数字なのかという判断をしますが、Fortranの場合は、文字列を数値を混在させるのは面倒なので、最初のカラムが数値ならば「-1」で、文字列にするならば「end」でという使い分けをしたほうが良いでしょう…と fortran90 初心者は思う訳です(fortran77は昔やったことがあるけど)。「end」を終端にする場合は、先頭のカラムの文字列は捨てカラムにするのが良いですね。

data	10	21
data	11	22
data	12	23
data	13	24
end

のように、先頭のカラムを捨ててしまうわけです。実は、これが便利なのは先頭が「data」の時はデータ行をあらわして「end」の場合は終端、という形で1行を読み込んだときに判断が付くところです。perl で処理する時に楽だったりします。大学の頃は、フォーマットの前処理を perl でやって、最終的に fortran で読み込ませる方法を取っていました。

カテゴリー: 開発, Fortran | Fortranでファイルの読み書きRead/Writeする はコメントを受け付けていません

FortranにC++から文字列を渡す

文字列の受け渡しはなるべくしたくないのだが、設定やら名称取得やらで必要になってくる。
Fortran の場合は、NULL terminate ではないのでC++で扱うときにはちょっと厄介である…そういえば、stringクラスを使うことになるので、そのあたりも考えないと。
基本は、固定領域をとっておいて、あとからC++側でnullを付け加えるとok。

module structmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( s )
        character*20 s
        print *, 'in fstr1:[', s,']'
    end subroutine fstr1
    ! 文字列を渡す
    subroutine fstr2( s )
        character*20 s
        integer i
        ! * で埋める
        do i=1,10
            s(i:i) = '*'
        end do
    end subroutine fstr2
    ! 文字列を渡す
    subroutine fstr3( s )
        character*20 :: s
        s = 't.masuda'
    end subroutine fstr3
end module structmodule

文字列を引数にすると、C++の関数では長さを定義する変数が入る。これは数値の変数とは違って、値型で渡す。

extern "C" {
	// 文字列を渡す
	void STRUCTMODULE_mp_FSTR1( char *str, int count );
	// 文字列を受け取る
	void STRUCTMODULE_mp_FSTR2( char *str, int count );
	void STRUCTMODULE_mp_FSTR3( char *str, int count );
}
	char str1[20+1] = {0};
	strcpy( str1, &quot;t.masuda&quot; );
	STRUCTMODULE_mp_FSTR1( str1, sizeof(str1)-1);

	char str2[20+1] = {0};
	STRUCTMODULE_mp_FSTR2( str2, sizeof(str2)-1);
	cout << &quot;str2:[&quot; << str2 << &quot;]&quot; << endl;

	char str3[20+1] = {0};
	STRUCTMODULE_mp_FSTR3( str3, sizeof(str3)-1);
	cout << &quot;str3:[&quot; << str3 << &quot;]&quot; << endl;

戻り値で扱うことができるかは不明。引数で渡すほうが無難だろう。
string型との変換を考えると、適当なマクロをC++側に仕込むのがよかろう。

カテゴリー: 開発, C++, Fortran | 1件のコメント

FortranにC言語から固定配列を渡す

科学計算の場合、行列式や1次元配列、2次元配列が頻繁に出てくる…と思われる。
Fortran の場合、あまり可変配列を使わずに、あらかじめメモリ領域を確保する固定配列を使う…ってそうしていたのだが、最近はどうなのだろう。可変にするとメモリの確保に時間がかかってスピードに問題がでるので、固定長さの配列のほうが有利。

module structmodule
    implicit none
    ! struct define
    type F3D
        double precision :: x,y,z
    end type F3D
contains
    ! one array 
    integer function sum1( v, count )
        integer :: v(10)
        integer :: i, total
        integer :: count
        
        total = 0
        do i=1,count
            print *, 'in sum1: ', i, v(i) 
            total = total + v(i)
        end do
        sum1 = total
    end function sum1
    ! 二次元配列を渡す
    integer function sum2( v, cnti, cntj )
        integer :: v(3,4)
        integer :: cnti, cntj
        integer :: i,j
        integer :: total
        
        total = 0
        do i=1,cnti
            do j=1,cntj
                print *,i,j,v(i,j)
                total = total + v(i,j)
            end do
        end do
        sum2 = total
    end function sum2
end module structmodule

引き数で配列の数を渡しているが、内部で固定にしかならないので、この場合はあまり意味がない。
最大長をチェックしてオーバーフローにならないように気を付けるとか。

extern "C" {
	// 1次元配列
	int STRUCTMODULE_mp_SUM1( int ary[], int *count );
	// 2次元配列
	int STRUCTMODULE_mp_SUM2( int ary[][3], int *cnti, int *cntj );
}
	int v1[] = {1,2,3,4,5,6,7,8,9,10};
	int count = 10;
	int sum1 = STRUCTMODULE_mp_SUM1( v1, &count );
	cout << &quot;ans:&quot; << sum1 << endl;

	int v2[4][3] = {
		{1,2,3},
		{2,3,4},
		{3,4,5},
		{4,5,6},
	};
	int cnti = 3, cntj = 4;
	int sum2 = STRUCTMODULE_mp_SUM2( v2, &cnti, &cntj );
	cout << &quot;ans:&quot; << sum2 << endl;

注意しないといけないのは、Fortranは「1」始まりで、C++は「0」始まりということ。なので、Fortranで「3」番目にアクセスをすると、C++では添え字の「2」にアクセスする(0始まりなので、3番目の要素になる)。配列そのものを扱う場合には気にならないが、index を渡す場合には注意が必要。
Fortranのほうを0始まりにできるオプションもあるらしいのだが、元のソースに合わせるとすると標準的な1始まりの配列を扱うほうがよかろう。

あと、2次元配列の場合は、次元が逆のように見える。これは、Fortranのほうにあわせて作る。

カテゴリー: 開発, Fortran | FortranにC言語から固定配列を渡す はコメントを受け付けていません

Fortranの構造体(TYPE)をC++で扱う

Fortranへのデータの取り回しは、C言語と同じように構造体を使うとok。C++ならばクラスを使いたいところなのだが、Fotranで扱うことはできないので構造体(TYPE)で。

module structmodule
    implicit none
    ! struct define
    type F3D
        double precision :: x,y,z
    end type F3D
contains
    ! struct parameter
    double precision function flen( pt )
        type (F3D) pt
        flen = sqrt( pt%x**2 + pt%y**2 + pt%z**2 )
        return 
    end function
end module structmodule

Fortranの構造体は TYPE で定義して、メンバ変数には「%」を使って参照する。

構造体はC言語側でもう一度定義しないさないといけない。

// 構造体を呼び出す
extern "C" {
	struct F3D {
		double x,y,z ;
	};
	double STRUCTMODULE_mp_FLEN( struct F3D *pt );
}

型だけを合わせれば OK で、メンバ変数名は自由につけてよい…が、同じようにつけたほうが良かろう。
関数の引き数に使う場合は、integerの変数と同じようにポインタにする。

function の戻り値に構造体を使うことも可能だと思うのだが、メモリ関係がややこしくなりそうなので構造体のデータ受け渡しは、引数で行ったほうが無難。

カテゴリー: 開発, Fortran | Fortranの構造体(TYPE)をC++で扱う はコメントを受け付けていません

FortranのCOMMONブロックをC++から扱う

少しメモ書きとして垂れ流し状態にするので、ご容赦を。

Fortranの引数は、C言語のポインタに変換することを覚えておけば、引数で渡すときは特に問題なし。
ただし、いちいち大量の設定データなどを、CからFortranに渡すのは大変なので、初期値っぽく Fortran の common ブロックを使うのがよい。

! common block
module commodule
    implicit none
    integer posx, posy
    common /compos/ posx, posy
contains
    subroutine comsub1()
        print *, "comsub1: ", posx, posy
    end subroutine comsub1
end module commodule

のように、commodule というモジュールの中に、compos という名前の common ブロックを作っておく。compos は、commodule モジュール内で共有できるし、モジュールの外に出してしまえば所謂グローバル変数として扱うことができる。ただし、大きなプログラムになると変数名がバッティングするので、モジュール内に入れるかなんらの命名規則を作ったほうがよかろう。

extern "C" {
	// common でデータを共有する
	struct ccompos {
		int posx;
		int posy;
	};
	// Fortran側のデータを構造体で参照する
	// プレフィックスはつかない模様
	extern ccompos COMPOS;
	// intel fortran では、モジュール名+_mp_+関数名でマッピングする
	// モジュール名、関数名は大文字
	void COMMODULE_mp_COMSUB1( void );
}

C++から扱うときは、common ブロックのパラメータをそのまま構造体にする。この順番は fortran で定義した順番通りにする。名前のない common ブロックの場合はどうするのか不明。
データ領域は、fortran で作成したライブラリ内にあるので、C++からは extern で参照しておく。このときの名前が、fortran で定義した common ブロックの名称になる。
common ブロックへの読み書きは、fortran からも c++ からも同様に行える。

実は、先頭のアドレスだけわかれば後はポインタでアクセスが可能なので、構造体を定義しなくても名前からアクセスできるマクロを作れそうだが、あまりスマートにいきそうにないのでやめておく。

カテゴリー: 開発, C++, Fortran | FortranのCOMMONブロックをC++から扱う はコメントを受け付けていません

windows 環境で fortran と C++ を連携させる(intel fortranの場合)

お仕事用のメモです。

FORTRANとCが混在したプログラム – Nobuhito Mori
http://www.oceanwave.jp/index.php?FORTRAN%A4%C8C%A4%AC%BA%AE%BA%DF%A4%B7%A4%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0#ubf9712b
プログラミングの話:Fortran,C,C++ の連携
http://www.mlab.ice.uec.ac.jp/~ej-sib/prog/prog_mixed.html#fortran_from_other_C
Fortran プログラマーズガイド: 11 – C と Fortran のインタフェース
http://www.hiroshima-cu.ac.jp/japanese/IPC/hunet99/sun/WorkShop/ja/html_docs/fortran/prog_guide/11_cfort.doc.html
FORTRANとCが混在したプログラム – Nobuhito Mori
http://www.oceanwave.jp/index.php?FORTRAN%A4%C8C%A4%AC%BA%AE%BA%DF%A4%B7%A4%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0#v4d3436c
DLLのメイク
http://rakasaka.fc2web.com/propath/prodll_make.html#Command

と、Intel Fortran と Free Fortran 95 を使って windows 7 上で調べています。
要は、

  • Fortran で計算ロジックを lib/dll で作る
  • C/C++ からライブラリを呼び出す

ということをするので、その下調べ。
どうやら、巷で溢れてい情報は、linux 上の C/Fortran連携のようで、C言語の関数の最後にアンダーバーが付くというのは、windows 版だとちょっと違う…ってな具合です。Fortran側を「Func1」で定義しておいて、C言語側で「Func1_」で呼び出すというのは、gcc の仕様らしい。逆に言えば、Intel Fortran や Free Fortran
95 だと違っているんですよ、ってなことです。

■DLLの定義

subroutine DllSub1(x,y)

  ! この DLL のユーザーへのサブルーチン Dll1 の説明
  !
  !DEC$ ATTRIBUTES DLLEXPORT::DllSub1

  ! 変数
  integer, intent(in) :: x, y

  ! DllSub1 の本文

end subroutine DllSub1

integer function DllFunc1( x, y )

  ! この DLL のユーザーへのサブルーチン Dll1 の説明
  !
  !DEC$ ATTRIBUTES DLLEXPORT::DllFunc1

  ! 変数
  integer, intent(in) :: x, y

  ! DllFunc1 の本文
  DllFunc1 = x + y

end function DllFunc1

■Libraryの定義

! C++からの呼び出し
module callmodule
    implicit none
  contains
    ! 関数定義1
    integer function csub1( x, y )
        integer, intent(in):: x, y
        csub1 = x + y
        return
    end function csub1
end module callmodule

! 関数定義1
integer function csub2( x, y )
    integer, intent(in):: x, y
    csub2 = x + y
    return
end function csub2

■C++から呼出し

#include &quot;stdafx.h&quot;
#include <iostream>
using namespace std;

extern &quot;C&quot; {
	extern int CALLMODULE_mp_CSUB1( const int *, const int * );
	extern int CSUB2( const int *, const int * );
}

#define DllImport __declspec( dllimport )

extern &quot;C&quot; {
	DllImport void DLLSUB1( const int *, const int * );
	DllImport int  DLLFUNC1( const int *, const int * );
}

int _tmain(int argc, _TCHAR* argv[])
{
	cout << &quot;c and fortran programing&quot; << endl;

	int x = 10;
	int y = 10;
	int ans = CALLMODULE_mp_CSUB1(&x,&y);
	cout << &quot;ans:&quot; << ans  << endl;

	ans = CSUB2(&x,&y);
	cout << &quot;ans:&quot; << ans  << endl;

	DLLSUB1( &x, &y );
	ans = DLLFUNC1( &x, &y );
	cout << &quot;ans:&quot; << ans  << endl;

	return 0;
}

Intel Fortran の場合は上記の呼出ができます。gcc とは違って末尾に「_」が付きません。関数名称は、nm コマンドを使うと調べることができます。

>nm CallModule.obj

00000000 N .debug$S
00000000 N .debug$T
00000000 i .drectve
00000000 t .text
00000000 r .trace
00000001 a @feat.00
00000000 T _CALLMODULE.
00000006 T _CALLMODULE_mp_CSUB1

Fortran でライブラリを作って、C言語にリンクさせる場合には「CALLMODULE_mp_CSUB1」という定義か「CSUB2」という定義になります。「CALLMODULE」の部分は fortran の module 名ですね。関数名がぶつからないように module を使うと、それが名前に入ってくるという罠なんですが、まぁ、コンパイラ依存で書いて良いのならば、こんな風に直接名前を付けられます。module に入っていない場合は、実はC言語の定義「_CSUB2」のように先頭にアンダーバーが付いた名前になります。

DLLの関数として公開する場合は「!DEC$ ATTRIBUTES DLLEXPORT::DllSub1」のような属性をつけます。Fortranの場合は「!」以降がコメントになるので、コメントを使ったプラグラマです。これは独自文法になるので intel fortran のみかと。
C言語の呼び出しのほうは、通常のDLL呼出と同じで「__declspec( dllimport )」を使います。このあたりは定番です。Fortran の方からは、大文字で公開されるので fortran 内で「DllFunc1」のように小文字を使っていても、公開されている関数名は「DLLFUNC1」になります。

という訳で、このあたり初手でハマるので、ひとまず公開しておきます。

カテゴリー: 開発, Fortran | 7件のコメント

リモートコンピュータの画面をキャプチャする

acer w500 に win8 を試していて、何が困るかと言えばキーボードが無いことです。いや、USB があるのだから差し込めばいいのですが(iPadの場合は、Bluetoothキーボードか、ソフトウェアキーボードで我慢)、画面キャプチャが出来ないのが不便。キーボードを繋げば、Print Screen キーでキャプチャできるのはわかっているし、リモートデスクトップを使えばキャプチャができるのはわかっているのですが、画面そのままの状態が欲しい。iPad の場合は、電源+ホームボタンでOKなのですが、win8 の場合はどうなんでしょう?

試しに、winキー + 電源ボタンを押すと、ctrl+alt+del と同じ画面がでます。

■Mouse without Borders を使う

Mouse without Borders – k本的に無料ソフト・フリーソフト
http://www.gigafree.net/internet/remote/mousewithoutborders.html

つい先日知ったのですが、「Mouse without Borders」でリモートコンピュータの画面キャプチャができます。『タスクトレイ上のアイコンを右クリック → 「Get Screen Capture from」 から、他PC の
デスクトップ画像をキャプチャしたり、「Send Screen Capture to」 から、自PC のデスクトップ画像を
他のパソコンに送ることもできたりします』ってな具合で、うーむ、簡単にできますね。

実は、これを twitter で知った時に、画面キャプチャ用のツールを作っていたんですよね orz 実にタイムリー(?)というか、なんというか。

■自作してみる

マウス制御で print screen キーを押せばよいのですが、まぁ画面キャプチャだけの単体ツールを作ってみましょうということで。

こんな画面で、リモートコンピュータの全体の画面キャプチャとアクティブウィンドウの画面をキャプチャします。リモート先でも同じプログラムを動かしておいて、キャプチャする画面からホスト名を入れて「Scr」か「Act」ボタンを押すだけです。最初は win フォームのチープな画面だったのですが、Mouse without Borders に対抗して、Excel で画像を作ってみました。

こんな風に、キャプチャ先のコンピュータで同じプログラムを動かしておけばOKです。

■.NETリモートを使う

データのやり取りには、.NETリモートを使っています。tcp/ip で直接扱う方法もあるし、iis を使う方法もあるのですが、.NETリモートが一番手軽です。

.NET リモートでは同じクラスを共有して受け渡しをするので、まず MarshalByRefObject を継承するクラスを作ります。CapCom と CapComRemote という2つのクラスを作っているのは、同じプログラムでクライアント/サーバーを共有させているためです。別々のプログラムにする場合は、ひとつのクラスで十分です。

namespace RemoteScreenCap
{
	public class CapCom : MarshalByRefObject
	{
		/// <summary>
		/// 汎用コマンド送信
		/// </summary>
		/// <param name=&quot;cmd&quot;></param>
		/// <returns></returns>
		public string GoSendCmd( string cmd )
		{
			if (SendCmd != null)
			{
				return SendCmd(cmd);
			}
			return &quot;&quot;;
		}

		/// <summary>
		/// ホスト名を通知
		/// </summary>
		/// <param name=&quot;hostname&quot;></param>
		public void GoSendHostname(string hostname)
		{
			if (SendHostname != null)
			{
				SendHostname(hostname);
			}
		}

		/// <summary>
		/// スクリーンキャプチャ実行
		/// </summary>
		/// <param name=&quot;cmd&quot;></param>
		/// <returns></returns>
		public byte[] GoScreenCap(string cmd)
		{
			if (ScreenCap != null)
			{
				return ScreenCap(cmd);
			}
			return null;
		}

		public delegate string SendCmdHandler(string cmd);
		public event SendCmdHandler SendCmd;
		public delegate byte[] ScreenCapHandler(string cmd);
		public event ScreenCapHandler ScreenCap;
		public event Action<string> SendHostname;
	}

	/// <summary>
	/// リモート側の通信クラス
	/// </summary>
	public class CapComRemote : CapCom { }
}

キャプチャしたデータは byte[] 配列で受信します。Bitmap クラスは Marshaling されないので、一度 BMP 形式のデータに直すわけですね。
イベントは戻り値を持つことができるので、delegate と event を使って定義します。これが、戻り値を必要としない場合は、Action< > を使うとひとつの定義で済みます。こっちのほうが最近の流行りです。

■画面のキャプチャと送信

ひとまず、全文を晒しておきます。もう少し整理したら Git に上げておきます。

namespace RemoteScreenCap
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		// 自ホスト名
		private string HostName = &quot;&quot;;
		// リモート側のホスト名
		private string RemoteName = &quot;&quot;;
		// 要求マシンのホスト名(未使用)
		private string ClientName = &quot;&quot;;
		// 待ちポート番号
		private int PORT = 19000;

		private void Form1_Load(object sender, EventArgs e)
		{
			// ホスト名を表示
			this.HostName = System.Net.Dns.GetHostName();
			this.Text = string.Format(&quot;{0} - {1}&quot;, HostName, &quot;RemoteScreenCap&quot;);

			// コネクション待ち(リモート側の処理)
			// HTTPチャンネルを設定する
			HttpChannel ch = new HttpChannel(PORT);
			ChannelServices.RegisterChannel(ch, false);
			var chObj = new CapComRemote();
			RemotingServices.Marshal(chObj, &quot;chObj&quot;);
			// ハンドルを登録する
			chObj.SendCmd += new CapCom.SendCmdHandler(chObj_SendCmd);
			chObj.SendHostname += new Action<string>(chObj_SendHostname);
			chObj.ScreenCap += new CapCom.ScreenCapHandler(chObj_ScreenCap);
			// 受付を開始する
			ch.StartListening(null);
		}

		#region クライアント側の処理

		private bool remoteConnected = false;

		/// <summary>
		/// リモートコンピュータに接続
		/// </summary>
		/// <param name=&quot;sender&quot;></param>
		/// <param name=&quot;e&quot;></param>
		private void buttonConnect_Click(object sender, EventArgs e)
		{
			remoteConnected = RemoteConnect();
		}

		private bool RemoteConnect()
		{
			this.RemoteName = textRemote.Text;
			// 送信用のチャンネルを設定する
			RemotingConfiguration.RegisterWellKnownClientType(
				typeof(CapCom),
				string.Format(&quot;http://{0}:{1}/chObj&quot;, this.RemoteName, this.PORT));

			return true;
		}

		/// <summary>
		/// コマンドを送信
		/// </summary>
		/// <param name=&quot;sender&quot;></param>
		/// <param name=&quot;e&quot;></param>
		private void button1_Click(object sender, EventArgs e)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show(&quot;リモートコンピュータ {0} に接続できません&quot;, this.RemoteName);
					return;
				}
			}
			// コマンドを送信する
			var obj = new CapCom();
			string res = obj.GoSendCmd(textCommand.Text);
			MessageBox.Show(res);
		}

		/// <summary>
		/// 画面キャプチャ
		/// </summary>
		/// <param name=&quot;sender&quot;></param>
		/// <param name=&quot;e&quot;></param>
		private void button2_Click(object sender, EventArgs e)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show(&quot;リモートコンピュータ {0} に接続できません&quot;, this.RemoteName);
					return;
				}
			}
			// 画面キャプチャコマンドを送信
			var obj = new CapCom();
			byte[] data = obj.GoScreenCap(&quot;all&quot;);

			if (data == null)
			{
				MessageBox.Show(&quot;エラー発生&quot;, &quot;&quot;, MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			MemoryStream mem = new MemoryStream(data);
			Bitmap bmp = new Bitmap(mem);
			// mem.Close();
			/*
			this.BackgroundImage = null;
			this.BackgroundImage = bmp;
			this.Width = bmp.Width;
			this.Height = bmp.Height;
			*/
			Clipboard.SetImage(bmp);

		}
		#endregion

		#region リモート側の処理

		/// <summary>
		/// クライアントコンピュータ名を保存
		/// </summary>
		/// <param name=&quot;name&quot;></param>
		void chObj_SendHostname(string name)
		{
			this.ClientName = name;
		}

		byte[] chObj_ScreenCap(string cmd)
		{
			Bitmap bmp = null;
			if (cmd == &quot;active&quot;)
			{
				// アクティブウィンドウをキャプチャ
				bmp = ActiveWindowCapture();
			}
			else
			{
				// 画面全体をキャプチャ
				//Bitmapの作成
				bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
					Screen.PrimaryScreen.Bounds.Height);
				//Graphicsの作成
				Graphics g = Graphics.FromImage(bmp);
				//画面全体をコピーする
				g.CopyFromScreen(new Point(0, 0), new Point(0, 0), bmp.Size);
				//解放
				g.Dispose();
			}

			MemoryStream mem = new MemoryStream();
			bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Bmp);
			mem.Close();
			byte[] data = mem.ToArray();
			return data;
		}

		[StructLayout(LayoutKind.Sequential)]
		private struct RECT
		{
			public int left;
			public int top;
			public int right;
			public int bottom;
		}
		[DllImport(&quot;user32.dll&quot;)]
		private static extern IntPtr GetWindowDC(IntPtr hwnd);
		[DllImport(&quot;user32.dll&quot;)]
		private static extern IntPtr GetForegroundWindow();
		[DllImport(&quot;user32.dll&quot;)]
		private static extern int GetWindowRect(IntPtr hwnd,
			ref  RECT lpRect);
		[DllImport(&quot;user32.dll&quot;)]
		private static extern IntPtr GetDC(IntPtr hwnd);
		[DllImport(&quot;gdi32.dll&quot;)]
		private static extern int BitBlt(IntPtr hDestDC,
			int x,
			int y,
			int nWidth,
			int nHeight,
			IntPtr hSrcDC,
			int xSrc,
			int ySrc,
			int dwRop);
		[DllImport(&quot;user32.dll&quot;)]
		private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
		private const int SRCCOPY = 13369376;

		/// <summary>
		/// アクティブウィンドウをキャプチャ
		/// </summary>
		/// <returns></returns>
		private Bitmap ActiveWindowCapture()
		{
			IntPtr hWnd = GetForegroundWindow();
			IntPtr hDC = GetWindowDC(hWnd);
			//ウィンドウの大きさを取得
			RECT rect = new RECT();
			GetWindowRect(hWnd, ref rect);
			Bitmap bmp = new Bitmap(rect.right - rect.left, rect.bottom - rect.top);
			Graphics g = Graphics.FromImage(bmp);
			IntPtr gDC = g.GetHdc();
			BitBlt(gDC, 0, 0, bmp.Width, bmp.Height, hDC, 0, 0, SRCCOPY);
			g.ReleaseHdc(gDC);
			g.Dispose();
			ReleaseDC(hWnd, hDC);
			return bmp;
		}

		/// <summary>
		/// コマンド受信
		/// </summary>
		/// <param name=&quot;cmd&quot;></param>
		/// <returns></returns>
		string chObj_SendCmd(string cmd)
		{
			return this.HostName + &quot;:&quot; + DateTime.Now.ToString() + &quot;:&quot; + cmd;
		}
		#endregion

		private void remoteCapture(string cmd)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show(&quot;リモートコンピュータ {0} に接続できません&quot;, this.RemoteName);
					return;
				}
			}
			// 画面キャプチャコマンドを送信
			var obj = new CapCom();
			byte[] data = obj.GoScreenCap(cmd);

			if (data == null)
			{
				MessageBox.Show(&quot;エラー発生&quot;, &quot;&quot;, MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			MemoryStream mem = new MemoryStream(data);
			Bitmap bmp = new Bitmap(mem);
			Clipboard.SetImage(bmp);

			label2.Text = DateTime.Now.ToString() + &quot; completed.&quot;;
		}

		private void label1_Click(object sender, EventArgs e)
		{
			// 全画面のキャプチャ
			remoteCapture(&quot;all&quot;);
		}

		private void label3_Click(object sender, EventArgs e)
		{
			// アクティブな画面のキャプチャ
			remoteCapture(&quot;active&quot;);
		}
	}
}

■受信側のコード

ちょっとだけ解説をすると、以下の部分がリモートコンピュータ側のコネクション待ちです。HttpChannel クラスでチャンネルを作って登録するあたりは定番なコードになります。RemotingServices.Marshal メソッドで登録するクラスが内部変数になっていますが、本来は外側に出すべきかなと。まぁ、参照している間は消えないのでこれでOKです。
…が、.NETリモートの場合、この通信用のオブジェクトが不定期に解放されるのを防ぐために、lifttimeを無限大にしておくことをお奨めします。この話は別な機会に。

private void Form1_Load(object sender, EventArgs e)
{
	// ホスト名を表示
	this.HostName = System.Net.Dns.GetHostName();
	this.Text = string.Format(&quot;{0} - {1}&quot;, HostName, &quot;RemoteScreenCap&quot;);

	// コネクション待ち(リモート側の処理)
	// HTTPチャンネルを設定する
	HttpChannel ch = new HttpChannel(PORT);
	ChannelServices.RegisterChannel(ch, false);
	var chObj = new CapComRemote();
	RemotingServices.Marshal(chObj, &quot;chObj&quot;);
	// ハンドルを登録する
	chObj.SendCmd += new CapCom.SendCmdHandler(chObj_SendCmd);
	chObj.SendHostname += new Action<string>(chObj_SendHostname);
	chObj.ScreenCap += new CapCom.ScreenCapHandler(chObj_ScreenCap);
	// 受付を開始する
	ch.StartListening(null);
}

■送信側のコード

.NETリモートのクライアント側からは、http プロトコルを使います。受信側で定義したアドレスに対してリクエストを投げるわけですね。
受信した byte 配列から bitmap クラスを再作成するには、MemoryStream クラスを使います。まあ、詳細はコードを見て頂くということで。

private bool RemoteConnect()
{
	this.RemoteName = textRemote.Text;
	// 送信用のチャンネルを設定する
	RemotingConfiguration.RegisterWellKnownClientType(
		typeof(CapCom),
		string.Format(&quot;http://{0}:{1}/chObj&quot;, this.RemoteName, this.PORT));

	return true;
}

/// <summary>
/// 画面キャプチャ
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private void button2_Click(object sender, EventArgs e)
{
	if (remoteConnected == false)
	{
		// 接続
		if ((remoteConnected = RemoteConnect()) == false)
		{
			MessageBox.Show(&quot;リモートコンピュータ {0} に接続できません&quot;, this.RemoteName);
			return;
		}
	}
	// 画面キャプチャコマンドを送信
	var obj = new CapCom();
	byte[] data = obj.GoScreenCap(&quot;all&quot;);

	if (data == null)
	{
		MessageBox.Show(&quot;エラー発生&quot;, &quot;&quot;, MessageBoxButtons.OK, MessageBoxIcon.Error);
		return;
	}
	MemoryStream mem = new MemoryStream(data);
	Bitmap bmp = new Bitmap(mem);
	// mem.Close();
	/*
	this.BackgroundImage = null;
	this.BackgroundImage = bmp;
	this.Width = bmp.Width;
	this.Height = bmp.Height;
	*/
	Clipboard.SetImage(bmp);

}

metro アプリケーションの場合もキャプチャができるので、こんな風に画面をスライドしている途中の画面もキャプチャができます。

あと、キーボードを押さなくていいので、普通のデスクトップのメニューが出ているところとかも撮りやすいはずです。

ひとまず、実行ファイルのダウンロードはこちらから。
http://moonmile.net/up/RemoteScreenCap-0.1.zip

カテゴリー: 開発, C# | リモートコンピュータの画面をキャプチャする はコメントを受け付けていません

2012年4月.NETラボ勉強会で発表します

宣伝がてら。

.NET Lab > Events > .NETラボ 勉強会 2012年04月
http://dotnetlab.net/dnn/Events/NET%E3%83%A9%E3%83%9C%E5%8B%89%E5%BC%B7%E4%BC%9A2012%E5%B9%B404%E6%9C%88/tabid/113/Default.aspx

.NETラボ勉強会で発表をします。今回の勉強会のテーマ…は、雑多なので特にない模様(苦笑)。まあ、ひとまず直前に Microsoft Windows Developer Days (WDD) ホーム なるイベントがあるので、その内容も交えてということで、Windows 8 in Acer の話をします。

ここのところ、acer w500 に windows 8 cosumer preview を入れて四苦八苦しているのは、

  • スレート PC に入れて、タッチパネル動作で Wndows 8 が使えるのか/使いやすいのか?
  • iPad に比べて Windows 8 の優位性は?逆に劣位なところは?
  • 本格的にスレートPCとして使えるのか?顧客に勧められるのか?

ってなところです。前身に(?)Windows Phone や Windows Moble があるわけですが、どうも wndows 8 metro application の位置は利用者(ユーザー)にとって微妙な位置になりそうですね、ってところと、開発者(developer)としてどんな風に提案をしていけばよいのか、というのを模索している途中です。その途中経過ということで iPad の操作も交えて発表をしていきます。なので、今回は、解説というよりも「途中経過」な話になるかと思います。

imageimage

おまけとして、先日、グリニッジ天文台に行ってきたので GMT+0 のお土産を買ってきました。当日来て下さった方には抽選(多分、じゃんけんとか)でお配りしますので、お楽しみにってことで。

場所は、

■会場:晴海区民館
都営大江戸線勝どき駅下車A2a出口 徒歩10分
http://www.tafuka.co.jp/tokyo_chuo/tsukishima_harumi.html

なところなので、いつもの品川とは違うので注意を。私も遅れないように行かないと。

カテゴリー: 勉強会 | 2012年4月.NETラボ勉強会で発表します はコメントを受け付けていません