VC++からfortran DLLの呼び出しについて

[戻る]
fortran使い 2005/06/11(土) 11:11:27
こんにちはFORTRAN使いです。fortranに多くの財産があるのでVC++で使おうと考えてい
ます。mingw環境でdllhelper(http://www.nanotech.wisc.edu/%7Ekhan/software/gnu-
win32/dllhelpers.html)でfortran DLLを作成しVC++から呼び出そうとしています。
HMODULE hVBVCDLL = AfxLoadLibrary("f77dll.dll");
   if (hVBVCDLL != NULL)
        {
typedef float (*Dllfunc)();
Dllfunc dllf2;
dllf2 = (Dllfunc)GetProcAddress(hVBVCDLL,"dllf2_"); 
float result = (*dllf2)();
        }
引数なしの関数の呼び出しは上記のように成功はしていますがsubroutineで引数つきの
ものを呼び出そうとするとどうやっていいのかわかりません。引数は参照渡しというの
はわかっているのですが。
具体的にサンプルを示していただけるとありがたいのですが、よろしくお願いします。

エートリーぶ 2005/06/11(土) 15:43:01
「fortran使い」さん、はじめまして。

実は、「FORTRAN」は、16年前の情報処理試験で勉強したきり
コンパイルもしたことがありません。頭の中で動かしただけです。
結局、当時は仕事が忙しく試験も受けていません(追想)。

「引数が参照渡し」ということは、「C言語」では
「コールバイリファレンス」ともいって、
float result = (*dllf2)(&para); ← 引数の前に「&」を付加
になると思います。

引数「para」は使う前に定義して置いて下さい。
「DLL」の方の関数に値を与えて使うなら、内容も事前に
設定して下さい。逆に「DLL」の方の関数で設定したときは
コールした後で値を取得できると思います。
引数が2個以上になったときは、スタックへの積み方が
「パスカル型」か「C型」か、どちらになるか実験してください。

有識者の補足・訂正、お願いします。

シャノン 2005/06/11(土) 16:40:53
> 「引数が参照渡し」ということは、「C言語」では
> 「コールバイリファレンス」ともいって、
> float result = (*dllf2)(&para); ← 引数の前に「&」を付加
> になると思います。

この場合、宣言も

// * がつきます。anytype は任意の型名に読み替えてください。
typedef float (*Dllfunc)( anytype * para );

に変ります。
以上、C 言語の話。


C++ ではポインタの他に「参照」というモノが登場するので、関数の引数渡しは
「値渡し」「ポインタ渡し」「参照渡し」の3つになります。
C++ で「参照渡し」と言うと、3つめのものを指すことになり、この場合は

// ここに & がつきます。anytype は任意の型名に読み替えてください。
typedef float (*Dllfunc)( anytype & para );
Dllfunc dllf2;
dllf2 = (Dllfunc)GetProcAddress(hVBVCDLL,"dllf2_"); 

// こっちには & がつきません。
float result = (*dllf2)( para );

になります。
なお、先の C 言語の例は「ポインタ渡し」と呼ばれます。

ただし、参照は C++ に独自のものであり、他の言語とやり取りする場合は、ポインタ渡しのほ
うが一般的なようです。
また、他の言語ではポインタ渡しと参照渡しの区別が無いため、C++ の「ポインタ渡し」を他
の言語では「参照渡し」と呼ぶ場合があります。

まぎらわしいですが、ご注意ください。

fortran使い 2005/06/11(土) 16:51:52
エートリーぶ さんお返事ありがとうございす。
質問時の文はfunctionのテストでsubroutineのテスト文は以下のようになります。
HMODULE hm2 = AfxLoadLibrary("f77dllsub.dll");
            if (hm2 != NULL)
            {
typedef void (*Dllsub)(double test[]);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double testd[6],sampled;
(*dllsub1)(testd);
            }
これだと問題なくコンパイルができますが配列の中身を見ると値が帰ってきてません。
エートリーぶさんのご指摘どおり参照渡しのために以下のように変更してみました。

subroutineの引数をポインタとして宣言
typedef void (*Dllsub)(double *test[]);
subroutineの引数に参照渡しで代入
(*dllsub1)(&testd);これだと
error C2664: 'void (double *[])' : 1 番目の引数を 'double (*__w64 )[6]' か
ら 'double *[]' に変換できません。
となります。

fortran使い 2005/06/11(土) 17:05:39
シャノンさん 鋭いご指摘ありがとうございます。
ネックになっていたのは「参照渡し」の理解のようです。
Cもfortranもそこそこの年数使ってきたのですが、自分の知っている文法だけ
で汚い文を書いてきたもので、とろこで今の最終的な状況は
HMODULE hm2 = AfxLoadLibrary("f77dllsub.dll");
            if (hm2 != NULL)
            {
typedef void (*Dllsub)(double &test[6]);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double *testd[6],sampled;
(*dllsub1)(testd);
sampled = &testd[2];
CString sampleds;
sampleds.Format("%.2lf",sampled);
pDC->TextOut(0,200,sampleds);
            }
こんな感じです。
: error C2234: 'test' : 参照の配列は宣言できません。(typedefのとこ)
: 'double **__w64  ' から 'double' に変換できません。(実際に関数を呼ぶとこ)
参照渡しの理解不足ですね。基礎から勉強しなおしてみます。

シャノン 2005/06/11(土) 17:16:50
こんなのは参考になるでしょうか

http://www.microsoft.com/japan/developer/library/vccore/_core_mixed.2d.language_
programming_topics.htm

シャノン 2005/06/11(土) 17:20:41
URL が切れてしまいました。
ブラウザのアドレスバーにコピペして繋げてください。

で。
FORTRAN 側の関数は、double 型(8バイト浮動小数点)の参照を引数に取り、そこに値を返す
ものでよろしいですね?

であれば、こうです。

typedef void (*Dllsub)(double * test);
Dllsub dllsub1 dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double ret = 0.0;
(*dllsub1)(&testd);

シャノン 2005/06/11(土) 17:21:46
間違えました。

typedef void (*Dllsub)(double * test);
Dllsub dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double ret = 0.0;
(*dllsub1)(&ret);


これが正解です。

fortran使い 2005/06/11(土) 17:28:31
シャノンさんご親切にありがとうございます。

typedef void (*Dllsub)(double *test[6]);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double testd[6],sampled;
(*dllsub1)(&testd);

これだと
C2664: 'void (double *[])' : 1 番目の引数を 'double (*__w64 )[6]' から 'double 
*[]' に変換できません。
となります。
配列でやっているので問題が複雑になっているのかもしれません。
問題を単純化してつぶしていこうと考えています。

基本的に帰ってきてほしい引数を*で定義して&で代入すれば問題ないと考えていま
す。もしかしたらDLL環境であることが悪さしているのかもしれませんが。

fortran使い 2005/06/11(土) 17:39:00
とりあえず配列ではない引数のバージョンです。
typedef void (*Dllsub)(double *param);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf4_");
double mparam=0.0;
(*dllsub1)(&mparam);
CString sampleds;
sampleds.Format("%.2lf",mparam);
pDC->TextOut(0,200,sampleds);

コンパイル自体は問題ありません。(なぜ配列のものはだめなのでしょうか?)

ただ実行してみるとハンドルされていない例外が発生してとまってしまいます。

fortran使い 2005/06/11(土) 17:44:50
すみません。まちがいです。上記の
「ただ実行してみるとハンドルされていない例外が発生してとまってしまいます。」
はfortranDLLの作成上のミスでした。

fortranのsubroutineの引数の配列ではないバージョンは問題なく使えました。

あと少しでできそうです。

fortran使い 2005/06/11(土) 17:51:02
配列引数バージョンです。
typedef void (*Dllsub)(double *param[6]);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double mparam[6];
(*dllsub1)(&mparam);
CString sampleds;
sampleds.Format("%.2lf",mparam[2]);
pDC->TextOut(0,200,sampleds);

error C2664: 'void (double *[])' : 1 番目の引数を 'double (*__w64 )[6]' か
ら 'double *[]' に変換できません。(subroutineの呼び出しのところで)

ここからは完全にVC++の問題だとは思うのですが、配列ではないものでよくて
配列でだめですね。よくわかりません。

シャノン 2005/06/11(土) 18:33:40
double * test[ 6 ]
これはポインタの配列(double * 型の要素6個からなる配列)ですが

double ( * test )[ 6 ]
これは配列へのポインタ(double[ 6 ] 型の変数へのポインタ)です。

> typedef void (*Dllsub)(double *param[6]);

この関数は「ポインタの配列」を要求しますが

> double mparam[6];
> (*dllsub1)(&mparam);

渡しているのは「配列のポインタ」であることがわかるでしょうか。
(mparam は配列、&mparam は配列へのポインタです)

ただ、普通、こんなことは気にしません。
VC++ 側と Fortran 側で、配列の要素数が6個という合意があるのであれば、その先頭のポイ
ンタだけ渡せば事足ります。
つまり、

typedef void (*Dllsub)(double *param);
Dllsub dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf4_");
double param[ 6 ];
(*dllsub1)(param);

こんな感じで行けます(配列名は、その先頭要素のポインタ、つまり &param[ 0 ] と同義で
す)。

シャノン 2005/06/11(土) 18:37:24
> ただ実行してみるとハンドルされていない例外が発生してとまってしまいます。

呼び出し規約の不一致かも。

typedef void ( __stdcall * Dllsub )( double * param );

としてみてください。

fortran使い 2005/06/11(土) 18:54:38
シャノン さん
例外発生の件は早とちりして記入してしまいました。
すみません。
経緯は3つ上の書き込みのとおりです。
fortran DLL の作成ミスでDLLにない関数を呼び出そうとしたものでした。
簡略化した配列でない引数のモデルはうまくいきました。

いまはfortranのsubroutineの引数が配列のときのみだけコンパイルエラーが発生する
という問題で悩んでいます。

一応__stdcallもつけてみましたが効果はありませんでした。

エートリーぶ 2005/06/11(土) 19:28:21
シャノンさん、こんばんは。

「FORTRAN」の場合、引数の渡し方が「C++」でいう
「ポインタ渡し」(=他の言語でいう「参照渡し」)
しかないと決め打ちしてしまいした。(汗)

提案ですが、
まず「C++」コードから、Win32のLoadLibrary("f77dll.dll");等を噛ませ
「DLL」なしの生の「FORTRAN」コードをコールしたらどうなりますか?
これだと「DLL」の問題が除外されるので、純粋に「FORTRAN」と「C」の
インターフェースの問題になりますが、もう、なさったでしょうか?
サイトの来訪者(私も含めて)も楽しみにしています。

fortran使い 2005/06/11(土) 21:31:57
皆さんおそろいのようでありがとうございます。
やはり問題は「参照渡し」のようですね。
教科書をひっくり返していたらC++では配列の場合は基本的に参照渡しである。
というような表現がありました。それで、配列の宣言時に*を使いサブルーチン
に渡す際に&を使うとエラーになるのかもしれません。配列は参照渡しという表現を信
用して
typedef void (*Dllsub)(double param[6]);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double mparam[6];
(*dllsub1)(mparam);
CString sampleds;
sampleds.Format("%.2lf",mparam[2]);
pDC->TextOut(0,200,sampleds);
とするとコンパイルは通るのですが配列の中身に値が入っていません。
C++の文法が通ってもフォートランへは参照渡しになっていないということでしょうか。
(配列を明示的に参照渡しにするとエラーになる。)
ここら辺の結論を出すためにもエートリーぶさんの提案はいいかもしれませんね。
しかし、何をすればとなると具体的によくわかりません。とりあえず、この実験はVC++
ではできずにmingwなりのGCC環境でやるようになるのかとは思いますが

エートリーぶ 2005/06/11(土) 22:08:36
あくまでも、(「C++」の)「参照渡し」で行くつもりですネ?(微笑)
だとすると、かなり難しいかと思います。ご存知のように「FORTRAN」には
この機能がありませんので。
打開策として、(「C」でいうところの)配列の先頭アドレスを渡して勝負
かなと思いますが・・・(どうなる???)
いずれにしても、内部の実装に関連する「トリッキー」なコードになるかも。
サイトの来訪者としては楽しみではありますが、夜も更けました。
今は休息が最善かと思います、お体を大切に。おやすみなさい・・・。

Ban 2005/06/11(土) 23:22:17
> 教科書をひっくり返していたらC++では配列の場合は基本的に参照渡しである。

表現としては「値渡しができない」という方が適切な気が。


> 配列は参照渡しという表現を信用して

配列の名前は暗黙で先頭ポインタに変換されますし、
上のコードでも確かにポインタ渡し(=広義の参照渡し=変更可能)であってます。


> あくまでも、(「C++」の)「参照渡し」で行くつもりですネ?(微笑)

fortran使い さんにそういう拘りは見受けられないようですが.....。


他言語と結合する場合によく extern "C" 宣言したりしますが、
C++ としては C 以外との互換インターフェイスを特に用意していません。
この種の結合時には C 言語の機能をそのまま使うのが普通です。
そして、C 言語でいう参照渡しとはつまり、ポインタ渡しのことを指します。

相手も普通は C 言語用にインターフェイスを提供してきますし、
FORTRAN においてもおそらくそうなのではないかと思います。(FORTRAN は未経験...)


> 「パスカル型」か「C型」か、どちらになるか実験してください。

FORTRAN にしても C++ にしても、「Windows の DLL」として作成される設定なら
お約束として stdcall ではないかと推測しますが、このコードでは無指定ですね。
> typedef void (*Dllsub)(double param[6]);

> 一応__stdcallもつけてみましたが効果はありませんでした。
とはなってますが、C側は無指定時のデフォルト cdecl だったとして、
FORTRAN 側は(DLL用に?) stdcall になってたりしませんか。

fortran使い 2005/06/12(日) 00:26:12
BANさん こんにちは ちょっと混乱しています。C exterm..
を使えばいいのかななんとも思うのですが、基本的に勉強不足でして
試行錯誤がメインになっています。
fortranの方の仕様はよくわかりません。というのはdllhelperの構成が
fortranとCのプログラムとmakefileからなっていて私はfortranのところを
一般的な表現で書いているだけなので(そしてなんとなくやってみたら配列の引数問題
以外はうまくいってしまった。)インターフェースの部分はわかりません。
たぶん以下のCの部分がヒントにはなると思います。

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#include <stdio.h>

BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, 
                       LPVOID reserved /* Not used. */ );

BOOL APIENTRY
DllMain (
     HINSTANCE hInst /* Library instance handle. */ ,
     DWORD reason /* Reason this function is being called. */ ,
     LPVOID reserved /* Not used. */ )
{

  switch (reason)
    {
    case DLL_PROCESS_ATTACH:
      break;

    case DLL_PROCESS_DETACH:
      break;

    case DLL_THREAD_ATTACH:
      break;

    case DLL_THREAD_DETACH:
      break;
    }
  return TRUE;
}
こんな感じです。
もう自力では解決できない領域にきてしまった感じがします。
あと少しお力をお貸しください。

シャノン 2005/06/12(日) 01:16:26
> たぶん以下のCの部分がヒントにはなると思います。

申し訳ありませんが、なりません。

で、今になって気づいたわけですが。
お使いの FORTRAN コンパイラは何です?
あるいは、呼び出したい関数の FORTRAN 側の定義を書くとよいかも。

fortran使い 2005/06/12(日) 01:24:33
シャノン さんこんにちは
開発環境は一番初めに書いたとおり
mingw環境でDLLfile を作成するdllhelperで作っています。サイズも小さいし
一見の価値はあると思いますのでダウンロードしてみてはいかがでしょうか。

中身は中核となるfortranとインターフェースのCコンパイルを指定したmakefile
でなっています。fortranの部分は以下のようにしました。

      subroutine dllf1 ( mydim )
      implicit real*8(a-h,o-z)
      dimension mydim(0:5,0:10)
      do 100 i = 0 , 5
      do 100 j = 0 , 10
      mydim(i,j) = 20.0
  100 continue
      mydim(4,3)=1.4142
      return
      end
c
      subroutine dllf3 ( mydim )
      implicit real*8(a-h,o-z)
      dimension mydim(0:5)
      mydim(0) = 0.0
      mydim(1) = 1.0
      mydim(2) = 2.0
      mydim(3) = 3.0
      mydim(4) = 4.0
      mydim(5) = 5.0
      return
      end
c
c
      subroutine dllf4 ( param )
      implicit real*8(a-h,o-z)
      param=4649.0
      return
      end
c
      function dllf5 ( param )
      implicit real*8(a-h,o-z)
      dimension mydim(0:5)
      dllf5=0.0
      do 200 i = 0 , 5
       dllf5 = dllf5 + mydim(i)
  200 continue
      return
      end
c
c
      function dllf2 ()
      real dllf2
c
      write (*,*) 'in dllf2.'
      dllf2 = 3.14159
      return
      end
ここで間違っていたら恥ずかしいのですが
それから呼び出し規約についてはVC++の方で一通りは試してみたのですが
だめでした。

シャノン 2005/06/12(日) 01:59:31
そっか、mingw だから gcc が標準でついてるのか。

FORTRAN はさっぱりわかりませんが、見よう見まねで…

> implicit real*8(a-h,o-z)

変数名の先頭が a 〜 h と o 〜 z ではじまるものは、暗黙的に double 型とする、か
な。
この場合、m で始まる mydim の型は何になるんでせう?

> mydim(0) = 0.0

倍精度型の場合、必ず 0.0d0 と書け、と言っているページをいくつか見かけます。

シャノン 2005/06/12(日) 02:00:42
> この場合、m で始まる mydim の型は何になるんでせう?

んー…
i, j, k, l, m, n で始まる名前は、特に定義されていない限り整数型…か?

fortran使い 2005/06/12(日) 02:32:51
[[解決]]
シャノンさん ありがとうございます。
痛いところをつかれました。fortranは目をつぶってでも書けるという
自負とVC++に対する必要以上の恐怖からとんでもないケアレスミスを
していました。ご指摘のとおり整数型の宣言を直した途端目的どおりの
動作を確認しました。

最終的な結果は以下のとおりです。
typedef void (*Dllsub)(double *param);
Dllsub dllsub1;
dllsub1 = (Dllsub)GetProcAddress(hm2,"dllf3_");
double mparam[6];
(*dllsub1)(mparam);
CString sampleds;
sampleds.Format("%.2lf",mparam[2]);
pDC->TextOut(0,200,sampleds);
配列はもともと参照渡しなので&をつけなくてもよい(つけるとエラーになる)
というのが結果でした。
解決までお付き合いいただいた皆様本当にありがとうございました。

ただ、今回やってみて気づいたことはfortranの財産を持っている人が多い割には
情報が少ないということです。
この結果は個人的にまとめてネットで公開しようかと考えています。
今までお付き合いありがとうございました。

古いスレッドには追加発言できません。