Translate

2016/05/26

TRNSYSの関数をC/C++から呼び出すと動かない

TRNSYS-USERSにC/C++からの関数呼び出しの話題が流れていました。

[TRNSYS-users] C++ API not working

C/C++のコンポーネントから整数値を返す関数を呼び出すとNaNが返ってくるとか、文字列を返す関数を使うとAccess Violationでクラッシュするとか。なかなか興味深い質問なのでしばらく注目してました。でも誰も返事がない。C/C++でコンポーネントを書いている人ってあんまりいないんですね、たぶん。

試しに書いてみたら、前者のNaNが返ってくるのは再現できませんでした。自分で書いてみたらちゃんと動く。質問者と何が違うんだろうと考えてみたんですが、これ原因はおそらく関数の呼び出しスタイルがTRNSYS16かTRNSYS17の違いじゃないかなー、と思います。どちらのスタイルで書くかによってTRNSYS側の挙動が違っていた気がします。

問題は後者の方ですが、もともとTRNSYSはFORTRANを前提に作られているので、C/C++から呼び出すにはひと手間必要になります。文字列を返す関数は今まで使う機会がなかったので、実は試していませんでした。(単純に呼び出してみたら質問者と同じようにAccess Violationでクラッシュした)

で、いろいろ調べ始めたらFORTRANとC/C++って文字列の扱いが違うので、そのままじゃやり取りできないんですね。(FORTRANは固定長で、C/C++はNULL終端で云々。。。)詳しくはこちら↓

[日本語]
インテル® Fortran コンパイラー 14.0 ユーザー・リファレンス・ガイド
文字データ型の戻り

[英語版]
User and Reference Guide for the Intel® Fortran Compiler 15.0
Returning Character Data Types

この記事の例を参考に、もともとgetDeckFileName()の関数をベースに新しいC/C++用に新しく関数getDeckFileNameEx() を追加してみました。

・TrnsysFunciton.f90
Function getDeckFileNameEx() bind(c,name="getDeckFileNameEx")
!dec$ attributes dllexport :: getDeckFileNameEx
Use TrnsysData
Use, intrinsic :: iso_c_binding
type(C_ptr) :: getDeckFileNameEx
Character(len=maxPathLength) ret 
ret = trim(deckn1)//CHAR(0)
getDeckFileNameEx = C_LOC(ret)
End Function getDeckFileNameEx

C/C++の呼び出し側はこんな感じで記述。。。

・TRNSYS.h
extern "C" __declspec(dllimport) char*    _cdecl getDeckFileNameEx(void);

・Type201.cpp
char *dckfilename;
dckfilename = getDeckFileNameEx();

こうするとちゃんと文字列でファイル名が取得できました。
一応対策はできたんですが、毎回毎回これと同じように関数を追加するのもなんかなー。それにFORTRANのソースコードの変更とリビルドが必要になるのでコンパイラも必要になる。ちょっと一般向けにはお勧めしにくい。他の方法ないかなー、というのが今日のひとまずの結論。

2017/4/15追記
TrnsysFunciton.f90に手を加えずに直接C/C++から関数を呼び出す方法をまとめました。
動いたけど半信半疑

0 件のコメント:

コメントを投稿